daily commit

This commit is contained in:
Dennis Nemec
2026-02-05 10:46:30 +01:00
parent 00ff9a7474
commit 8419c77263
46 changed files with 5494 additions and 3 deletions

4
.env Normal file
View File

@ -0,0 +1,4 @@
POSTGRES_USER="admin"
POSTGRES_PASSWORD="admin"
KEYCLOAK_ADMIN_PASSWORD="admin"
KC_HOSTNAME="localhost"

10
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

345
.idea/editor.xml generated Normal file
View File

@ -0,0 +1,345 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BackendCodeEditorSettings">
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CDeclarationWithImplicitIntType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConstevalIfIsAlwaysConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractClassWithoutSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractVirtualFunctionCallInCtor/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAccessSpecifierWithNoDeclarations/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAwaiterTypeIsNotClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBooleanIncrementExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatBadCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatLegacyCode/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatMixedArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooFewArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCompileTimeConstantCanBeReplacedWithBooleanConstant/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConceptNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConditionalExpressionCanBeSimplified/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCoroutineCallResolveError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesLocal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationSpecifierWithoutDeclarators/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorDisambiguatedAsFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorUsedBeforeInitialization/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultCaseNotHandledInSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedEntity/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedRegisterStorageClassSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDereferenceOperatorLimitExceeded/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDiscardedPostfixOperatorResult/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenSyntaxError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUndocumentedParameter/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUnresolvedReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEmptyDeclaration/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersOrder/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersPlacement/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceDoStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceForStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceFunctionDeclarationStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceIfStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceNestedNamespacesStyle/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingDestructorStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingFunctionStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceTypeAliasCodeStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceWhileStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityAssignedButNoRead/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityUsedOnlyInUnevaluatedContext/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnumeratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEqualOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEvaluationFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExplicitSpecializationInNonNamespaceScope/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExpressionWithoutSideEffects/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalFunctionInFinalClass/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalNonOverridingVirtualFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForLoopCanBeReplacedWithWhile/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForwardEnumDeclarationWithoutUnderlyingType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionDoesntReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionResultShouldBeUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionalStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHeaderHasBeenAlreadyIncluded/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHiddenFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHidingFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIdenticalOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIfCanBeReplacedByConstexprIf/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtDoubleUserConversionInCopyInit/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtNotInitializedStaticConstLocalVar/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtReinterpretCastFromNullptr/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterWideLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMustBePublicVirtualToImplementInterface/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMutableSpecifierOnReferenceMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNoDiscardExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNodiscardFunctionWithoutReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExceptionSafeResourceAcquisition/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConversionOperator/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConvertingConstructor/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineFunctionDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineVariableDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPassValueParameterByConstReference/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerConversionDropsQualifiers/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerToIntegralConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPolymorphicClassWithNonVirtualPublicDestructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyErroneousEmptyStatements/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUnintendedObjectSlicing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderIsNotIncluded/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderNotFound/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfBadFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfExtraArg/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfMissedArg/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfRiskyFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrivateSpecialMemberFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRangeBasedForIncompatibleReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedefinitionOfDefaultArgumentInOverrideFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBooleanExpressionArgument/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantCastExpression/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantComplexityInComparison/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConditionalExpression/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConstSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantControlFlowJump/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantDereferencingAndTakingAddress/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElaboratedTypeSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeyword/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeywordInsideCompoundStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyDeclaration/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantExportKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantInlineSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantLambdaParameterList/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantMemberInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantNamespaceDefinition/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantParentheses/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnMemberAllocationFunction/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnThreadLocalLocalVariable/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateArguments/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantVoidArgumentList/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantZeroInitializerInAggregateInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReinterpretCastFromVoidPtr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRemoveRedundantBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceMemsetWithZeroInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceTieWithStructuredBinding/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReturnNoValueInNonVoidFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSmartPointerVsMakeFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSomeObjectMembersMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSpecialFunctionWithoutNoexceptSpecification/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticAssertFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppThrowExpressionCanBeReplacedWithRethrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScope/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScopeInitStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTypeAliasNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedDependentBaseClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedNonStaticDataMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnionMemberOfReferenceType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAssociativeContains/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAuto/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAutoForNumeric/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseElementsView/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseEraseAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseFamiliarTemplateSyntaxForGenericLambdas/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseRangeAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStdSize/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStructuredBinding/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseTypeTraitAlias/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUserDefinedLiteralSuffixDoesNotStartWithUnderscore/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUsingResultOfAssignmentAsCondition/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVariableCanBeMadeConstexpr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionCallInsideCtor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionInFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVolatileParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWarningDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongIncludesOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongSlashesInIncludeDirective/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroConstantCanBeReplacedWithNullptr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroValuedExpressionUsedAsNullPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppClangFormat/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_ARGUMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXPRESSION/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_EXTENDS_LIST/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_FOR_STMT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_PARAMETER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_ARGUMENT/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTILINE_TYPE_PARAMETER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_MULTIPLE_DECLARATION/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue" value="ALIGN_ALL" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_CLASS_DEFINITION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_DECLARATIONS/@EntryValue" value="0" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DECLARATION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BLANK_LINES_AROUND_FUNCTION_DEFINITION/@EntryValue" value="1" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/BREAK_TEMPLATE_DECLARATION/@EntryValue" value="LINE_BREAK" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CONTINUOUS_LINE_INDENT/@EntryValue" value="Double" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_ACCESS_SPECIFIERS_FROM_CLASS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CLASS_MEMBERS_FROM_ACCESS_SPECIFIERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_COMMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_SIZE/@EntryValue" value="4" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_STYLE/@EntryValue" value="Space" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INITIALIZER_BRACES/@EntryValue" value="END_OF_LINE_NO_SPACE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_EQ/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_CODE/@EntryValue" value="2" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue" value="2" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/KEEP_USER_LINEBREAKS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/LINE_BREAK_AFTER_COLON_IN_MEMBER_INITIALIZER_LISTS/@EntryValue" value="ON_SINGLE_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/MEMBER_INITIALIZER_LIST_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue" value="All" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_NAMESPACE_DEFINITIONS_ON_SAME_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_BLOCK_STYLE/@EntryValue" value="DO_NOT_CHANGE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_COMMA_IN_TEMPLATE_PARAMS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_FOR_SEMICOLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_METHOD/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_PTR_IN_NESTED_DECLARATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBER/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_DATA_MEMBERS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_REF_IN_METHOD/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_UNARY_OPERATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_COLON_IN_BITFIELD_DECLARATOR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_EXTENDS_COLON/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_COLON/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_FOR_SEMICOLON/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_PTR_IN_METHOD/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_ABSTRACT_DECL/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBER/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_DATA_MEMBERS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_REF_IN_METHOD/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BEFORE_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_BETWEEN_CLOSING_ANGLE_BRACKETS_IN_TEMPLATE_ARGS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_ARRAY_ACCESS_BRACKETS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_DECLARATION_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_BLOCKS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_METHOD_PARENTHESES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_EMPTY_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_INITIALIZER_BRACES/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_ARGS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_WITHIN_TEMPLATE_PARAMS/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPECIAL_ELSE_IF_TREATMENT/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TAB_WIDTH/@EntryValue" value="4" type="int" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_BINARY_OPSIGN/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_AFTER_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_ARGUMENTS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_DECLARATION_RPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_LPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_INVOCATION_RPAR/@EntryValue" value="false" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_BEFORE_TERNARY_OPSIGNS/@EntryValue" value="true" type="bool" />
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_PARAMETERS_STYLE/@EntryValue" value="WRAP_IF_LONG" type="string" />
<option name="/Default/CodeStyle/EditorConfig/EnableClangFormatSupport/@EntryValue" value="false" type="bool" />
</component>
</project>

11
.idea/gas-delivery-backend.iml generated Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

46
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id>Async code and promisesJavaScript and TypeScript</id>
</State>
<State>
<id>C/C++</id>
</State>
<State>
<id>JavaScript and TypeScript</id>
</State>
<State>
<id>Pandas</id>
</State>
<State>
<id>Potential Code Quality IssuesC/C++</id>
</State>
<State>
<id>Python</id>
</State>
<State>
<id>ReactJavaScript and TypeScript</id>
</State>
<State>
<id>RegExp</id>
</State>
<State>
<id>TypeScriptJavaScript and TypeScript</id>
</State>
<State>
<id>XPath</id>
</State>
</expanded-state>
<selected-state>
<State>
<id>User defined</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/gas-delivery-backend.iml" filepath="$PROJECT_DIR$/.idea/gas-delivery-backend.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

2954
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

21
Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "delivery-backend"
version = "0.1.0"
edition = "2024"
[dependencies]
axum = "0.8.6"
axum-keycloak-auth = "0.8.3"
chrono = "0.4.42"
log = "0.4.28"
redis = { version = "0.32.6", features = ["connection-manager", "tokio-comp"] }
reqwest = { version = "0.12.23", features = ["json"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
simplelog = "0.12.2"
tokio = { version = "1.47.1", features = ["full"] }
toml = "0.9.7"
oauth2 = "5.0.0"
uuid = "1.18.1"
axum-extra = { version = "0.10.3", features = ["cookie", "typed-header", "json-deserializer"] }
base64 = "0.22.1"

48
Dockerfile Normal file
View File

@ -0,0 +1,48 @@
# Build stage
FROM rust:1.90.0-slim-trixie as builder
# Create app directory
WORKDIR /app
# Copy manifests
COPY Cargo.toml Cargo.lock ./
# Copy source code
COPY src ./src
# Install runtime dependencies (if needed, e.g., for SSL)
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates libssl-dev pkg-config && \
rm -rf /var/lib/apt/lists/*
# Build for release
RUN cargo build --release
# Runtime stage
FROM debian:bookworm-slim
# Install runtime dependencies (if needed, e.g., for SSL)
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates libssl-dev && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -m -u 1000 appuser
# Set working directory
WORKDIR /app
# Copy only the binary from builder
COPY --from=builder /app/target/release/delivery-backend /app/service
# Change ownership to non-root user
RUN chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Expose port (adjust as needed)
EXPOSE 8080
# Run the binary
CMD ["/app/service"]

View File

@ -1,3 +1 @@
# gas-delivery-backend
Das Projekt dient als Testumgebung, ohne auf GSD-Software angewiesen zu sein.
# Lieferservice Rust Backend

20
config.toml Normal file
View File

@ -0,0 +1,20 @@
log_file_prefix = "delivery_backend"
host_ip = "127.0.0.1"
host_port = 3000
redis_url = "redis://127.0.0.1:6379"
frontend_url = "myapp://callback"
gsd_app_key = "GSD-RestApi"
gsd_rest_url = "http://127.0.0.1:8334"
gsd_user = "<GSD-USER>"
gsd_password = "<GSD-Password>"
gsd_app_names = ["GSD-RestApi"]
[keycloak]
realm_url = "http://127.0.0.1:8080/realms/master"
client_id = "delivery-app"
client_secret = "Re9pifaHT78PY5CjE3UaCiAxyq5oawTU"
auth_url = "http://127.0.0.1:8080/realms/master/protocol/openid-connect/auth"
token_url = "http://127.0.0.1:8080/realms/master/protocol/openid-connect/token"
redirect_url = "http://127.0.0.1:3000/callback"
realm = "master"
base_url = "http://127.0.0.1:8080"

20
config.toml.example Normal file
View File

@ -0,0 +1,20 @@
log_file_prefix = "delivery_backend"
host_ip = "127.0.0.1"
host_port = 3000
redis_url = "redis://127.0.0.1:6379"
gsd_app_key = "GSD-RestApi"
frontend_url = "http://127.0.0.1:3000"
gsd_rest_url = "http://127.0.0.1:8334"
gsd_user = "GSDWebServiceTmp"
gsd_password = "admin"
gsd_app_names = ["GSD-RestApi"]
[keycloak]
realm_url = "http://localhost:8080/realms/master"
client_id = "delivery-app"
client_secret = "<SECRET>"
realm = "<REALM>>"
base_url = "<BASE_URL>"
auth_url = "http://localhost:8080/realms/master/protocol/openid-connect/auth"
token_url = "http://localhost:8080/realms/master/protocol/openid-connect/token"
redirect_url = "http://127.0.0.1:3000/callback"

View File

@ -0,0 +1,17 @@
11:43:33 [INFO] Redirect URI: http://127.0.0.1:3000/callback
11:43:33 [INFO] Logging initialized
11:43:33 [INFO] Starting Gas Delivery Backend
11:43:33 [INFO] Initializing redis server
11:43:33 [INFO] Starting axum server
11:43:33 [INFO] perform_oidc_discovery; kc_instance_id=019c2350-a621-7891-b1e7-8cd16bb3ea5d kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
11:43:33 [INFO] layer; id=019c2350-a621-7891-b1e7-8cefbc7c05e3
11:43:33 [INFO] Starting OIDC discovery.
11:43:33 [INFO] layer; id=019c2350-a621-7891-b1e7-8cefbc7c05e3
11:43:33 [INFO] layer; id=019c2350-a621-7891-b1e7-8cefbc7c05e3
11:43:33 [INFO] layer; id=019c2350-a621-7891-b1e7-8cefbc7c05e3
11:43:33 [INFO] Listening on 127.0.0.1:3000
11:43:33 [INFO] Received new jwk_set containing 2 keys.
11:43:36 [INFO] Access token expired for session 019c233e-192e-7e01-9f6f-2420edb77530, attempting refresh
11:43:36 [INFO] Successfully refreshed access token for session 019c233e-192e-7e01-9f6f-2420edb77530
11:45:48 [INFO] Access token expired for session 019c233e-192e-7e01-9f6f-2420edb77530, attempting refresh
11:45:48 [INFO] Successfully refreshed access token for session 019c233e-192e-7e01-9f6f-2420edb77530

View File

@ -0,0 +1,14 @@
11:45:54 [INFO] Redirect URI: http://127.0.0.1:3000/callback
11:45:54 [INFO] Logging initialized
11:45:54 [INFO] Starting Gas Delivery Backend
11:45:54 [INFO] Initializing redis server
11:45:54 [INFO] Starting axum server
11:45:54 [INFO] perform_oidc_discovery; kc_instance_id=019c2352-cc18-7591-9646-c102294a1e16 kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
11:45:54 [INFO] layer; id=019c2352-cc19-7c60-9105-7ff0e0c191d3
11:45:54 [INFO] Starting OIDC discovery.
11:45:54 [INFO] layer; id=019c2352-cc19-7c60-9105-7ff0e0c191d3
11:45:54 [INFO] layer; id=019c2352-cc19-7c60-9105-7ff0e0c191d3
11:45:54 [INFO] layer; id=019c2352-cc19-7c60-9105-7ff0e0c191d3
11:45:54 [INFO] Listening on 127.0.0.1:3000
11:45:54 [INFO] Received new jwk_set containing 2 keys.
11:46:02 [INFO] Access token still valid for session 019c233e-192e-7e01-9f6f-2420edb77530 (expires in 46 seconds)

View File

@ -0,0 +1,15 @@
11:46:53 [INFO] Redirect URI: http://127.0.0.1:3000/callback
11:46:53 [INFO] Logging initialized
11:46:53 [INFO] Starting Gas Delivery Backend
11:46:53 [INFO] Initializing redis server
11:46:53 [INFO] Starting axum server
11:46:53 [INFO] perform_oidc_discovery; kc_instance_id=019c2353-b20d-7311-ac2a-05e277a1e841 kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
11:46:53 [INFO] layer; id=019c2353-b20e-73b2-92d0-194d33673bd9
11:46:53 [INFO] Starting OIDC discovery.
11:46:53 [INFO] layer; id=019c2353-b20e-73b2-92d0-194d33673bd9
11:46:53 [INFO] layer; id=019c2353-b20e-73b2-92d0-194d33673bd9
11:46:53 [INFO] layer; id=019c2353-b20e-73b2-92d0-194d33673bd9
11:46:53 [INFO] Listening on 127.0.0.1:3000
11:46:53 [INFO] Received new jwk_set containing 2 keys.
11:46:59 [INFO] Access token expired for session 019c233e-192e-7e01-9f6f-2420edb77530, attempting refresh
11:46:59 [INFO] Successfully refreshed access token for session 019c233e-192e-7e01-9f6f-2420edb77530

View File

@ -0,0 +1,14 @@
11:47:20 [INFO] Redirect URI: http://127.0.0.1:3000/callback
11:47:20 [INFO] Logging initialized
11:47:20 [INFO] Starting Gas Delivery Backend
11:47:20 [INFO] Initializing redis server
11:47:20 [INFO] Starting axum server
11:47:20 [INFO] perform_oidc_discovery; kc_instance_id=019c2354-1a24-77f1-9e59-1653ab679598 kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
11:47:20 [INFO] layer; id=019c2354-1a24-77f1-9e59-166e393ee02f
11:47:20 [INFO] Starting OIDC discovery.
11:47:20 [INFO] layer; id=019c2354-1a24-77f1-9e59-166e393ee02f
11:47:20 [INFO] layer; id=019c2354-1a24-77f1-9e59-166e393ee02f
11:47:20 [INFO] layer; id=019c2354-1a24-77f1-9e59-166e393ee02f
11:47:20 [INFO] Listening on 127.0.0.1:3000
11:47:20 [INFO] Received new jwk_set containing 2 keys.
11:47:29 [INFO] Access token still valid for session 019c233e-192e-7e01-9f6f-2420edb77530 (expires in 30 seconds)

View File

@ -0,0 +1,23 @@
11:47:53 [INFO] Redirect URI: http://127.0.0.1:3000/callback
11:47:53 [INFO] Logging initialized
11:47:53 [INFO] Starting Gas Delivery Backend
11:47:53 [INFO] Initializing redis server
11:47:53 [INFO] Starting axum server
11:47:53 [INFO] perform_oidc_discovery; kc_instance_id=019c2354-9b2b-77f0-958c-55e31e57410c kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
11:47:53 [INFO] layer; id=019c2354-9b2b-77f0-958c-55f0558b380d
11:47:53 [INFO] Starting OIDC discovery.
11:47:53 [INFO] layer; id=019c2354-9b2b-77f0-958c-55f0558b380d
11:47:53 [INFO] layer; id=019c2354-9b2b-77f0-958c-55f0558b380d
11:47:53 [INFO] layer; id=019c2354-9b2b-77f0-958c-55f0558b380d
11:47:53 [INFO] Listening on 127.0.0.1:3000
11:47:53 [INFO] Received new jwk_set containing 2 keys.
12:03:31 [INFO] Callback called
12:03:31 [INFO] Successfully created session 019c2362-edbd-77b1-a0bd-8c29f4bdf257 for user
12:03:31 [INFO] Token scopes: EmptyExtraTokenFields
12:03:32 [INFO] Access token still valid for session 019c2362-edbd-77b1-a0bd-8c29f4bdf257 (expires in 59 seconds)
12:03:56 [INFO] Callback called
12:03:56 [INFO] Successfully created session 019c2363-4d78-74c2-96db-9e1faced3c74 for user
12:03:56 [INFO] Token scopes: EmptyExtraTokenFields
12:03:56 [INFO] Access token still valid for session 019c2363-4d78-74c2-96db-9e1faced3c74 (expires in 60 seconds)
12:05:45 [INFO] Access token expired for session 019c233e-192e-7e01-9f6f-2420edb77530, attempting refresh
12:05:45 [INFO] Successfully refreshed access token for session 019c233e-192e-7e01-9f6f-2420edb77530

View File

@ -0,0 +1,13 @@
12:08:35 [INFO] Redirect URI: http://127.0.0.1:3000/callback
12:08:35 [INFO] Logging initialized
12:08:35 [INFO] Starting Gas Delivery Backend
12:08:35 [INFO] Initializing redis server
12:08:35 [INFO] Starting axum server
12:08:35 [INFO] perform_oidc_discovery; kc_instance_id=019c2367-8f4e-7ec1-809b-abded76bf656 kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
12:08:35 [INFO] layer; id=019c2367-8f4e-7ec1-809b-abeed90ad1b9
12:08:35 [INFO] Starting OIDC discovery.
12:08:35 [INFO] layer; id=019c2367-8f4e-7ec1-809b-abeed90ad1b9
12:08:35 [INFO] layer; id=019c2367-8f4e-7ec1-809b-abeed90ad1b9
12:08:35 [INFO] layer; id=019c2367-8f4e-7ec1-809b-abeed90ad1b9
12:08:35 [INFO] Listening on 127.0.0.1:3000
12:08:35 [INFO] Received new jwk_set containing 2 keys.

View File

@ -0,0 +1,25 @@
12:10:33 [INFO] Redirect URI: http://127.0.0.1:3000/callback
12:10:33 [INFO] Logging initialized
12:10:33 [INFO] Starting Gas Delivery Backend
12:10:33 [INFO] Initializing redis server
12:10:33 [INFO] Starting axum server
12:10:33 [INFO] perform_oidc_discovery; kc_instance_id=019c2369-5d36-74f2-a278-d98f6f997d4c kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
12:10:33 [INFO] Starting OIDC discovery.
12:10:33 [INFO] layer; id=019c2369-5d36-74f2-a278-d993e2a77fb3
12:10:33 [INFO] layer; id=019c2369-5d36-74f2-a278-d993e2a77fb3
12:10:33 [INFO] layer; id=019c2369-5d36-74f2-a278-d993e2a77fb3
12:10:33 [INFO] layer; id=019c2369-5d36-74f2-a278-d993e2a77fb3
12:10:33 [INFO] Listening on 127.0.0.1:3000
12:10:33 [INFO] Received new jwk_set containing 2 keys.
12:10:52 [INFO] Callback called
12:10:52 [INFO] Successfully created session 019c2369-a8af-74c3-b516-6ab502c10865 for user
12:10:52 [INFO] Token scopes: EmptyExtraTokenFields
12:10:53 [INFO] Access token still valid for session 019c2369-a8af-74c3-b516-6ab502c10865 (expires in 59 seconds)
13:57:06 [INFO] Callback called
13:57:06 [INFO] Successfully created session 019c23ca-e7f5-7702-87b3-d30e6b39caea for user
13:57:06 [INFO] Token scopes: EmptyExtraTokenFields
13:57:06 [INFO] Access token still valid for session 019c23ca-e7f5-7702-87b3-d30e6b39caea (expires in 60 seconds)
14:47:54 [INFO] Callback called
14:47:54 [INFO] Successfully created session 019c23f9-6cdd-7bc1-9221-6fd100ba718b for user
14:47:54 [INFO] Token scopes: EmptyExtraTokenFields
14:47:55 [INFO] Access token still valid for session 019c23f9-6cdd-7bc1-9221-6fd100ba718b (expires in 59 seconds)

View File

@ -0,0 +1,17 @@
12:17:23 [INFO] Redirect URI: http://127.0.0.1:3000/callback
12:17:23 [INFO] Logging initialized
12:17:23 [INFO] Starting Gas Delivery Backend
12:17:23 [INFO] Initializing redis server
12:17:23 [INFO] Starting axum server
12:17:23 [INFO] perform_oidc_discovery; kc_instance_id=019c2895-f887-7350-b9f6-6c8f37d12eb6 kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
12:17:23 [INFO] layer; id=019c2895-f888-7bc3-a601-00e1535f7d9d
12:17:23 [INFO] Starting OIDC discovery.
12:17:23 [INFO] layer; id=019c2895-f888-7bc3-a601-00e1535f7d9d
12:17:23 [INFO] layer; id=019c2895-f888-7bc3-a601-00e1535f7d9d
12:17:23 [INFO] layer; id=019c2895-f888-7bc3-a601-00e1535f7d9d
12:17:23 [INFO] Listening on 127.0.0.1:3000
12:17:23 [INFO] Received new jwk_set containing 2 keys.
12:17:38 [INFO] Callback called
12:17:38 [INFO] Successfully created session 019c2896-34eb-7ed0-9b1a-59f54c9a44c9 for user
12:17:38 [INFO] Token scopes: EmptyExtraTokenFields
12:17:38 [INFO] Access token still valid for session 019c2896-34eb-7ed0-9b1a-59f54c9a44c9 (expires in 60 seconds)

View File

@ -0,0 +1,20 @@
12:40:32 [INFO] Redirect URI: http://127.0.0.1:3000/callback
12:40:32 [INFO] Logging initialized
12:40:32 [INFO] Starting Gas Delivery Backend
12:40:32 [INFO] Initializing redis server
12:40:32 [INFO] Starting axum server
12:40:32 [INFO] perform_oidc_discovery; kc_instance_id=019c28ab-2a25-7051-b0b2-83f13f1f783f kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
12:40:32 [INFO] Starting OIDC discovery.
12:40:32 [INFO] layer; id=019c28ab-2a25-7051-b0b2-84014d120574
12:40:32 [INFO] layer; id=019c28ab-2a25-7051-b0b2-84014d120574
12:40:32 [INFO] layer; id=019c28ab-2a25-7051-b0b2-84014d120574
12:40:32 [INFO] layer; id=019c28ab-2a25-7051-b0b2-84014d120574
12:40:32 [INFO] layer; id=019c28ab-2a25-7051-b0b2-84014d120574
12:40:32 [INFO] layer; id=019c28ab-2a25-7051-b0b2-84014d120574
12:40:32 [INFO] Listening on 127.0.0.1:3000
12:40:32 [INFO] Received new jwk_set containing 2 keys.
12:41:38 [INFO] Callback called
12:41:38 [INFO] Successfully created session 019c28ac-2f5a-75e1-b909-f862e773a5c6 for user
12:41:38 [INFO] Token scopes: EmptyExtraTokenFields
12:41:39 [INFO] Access token still valid for session 019c28ac-2f5a-75e1-b909-f862e773a5c6 (expires in 59 seconds)
12:42:33 [INFO] Access token still valid for session 019c28ac-2f5a-75e1-b909-f862e773a5c6 (expires in 5 seconds)

View File

@ -0,0 +1,42 @@
12:44:41 [INFO] Redirect URI: http://127.0.0.1:3000/callback
12:44:41 [INFO] Logging initialized
12:44:41 [INFO] Starting Gas Delivery Backend
12:44:41 [INFO] Initializing redis server
12:44:41 [INFO] Starting axum server
12:44:41 [INFO] perform_oidc_discovery; kc_instance_id=019c28ae-f9fa-7ad0-b4b8-4c583e97ed32 kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
12:44:41 [INFO] Starting OIDC discovery.
12:44:41 [INFO] layer; id=019c28ae-f9fa-7ad0-b4b8-4c60b26bdea9
12:44:41 [INFO] layer; id=019c28ae-f9fa-7ad0-b4b8-4c60b26bdea9
12:44:41 [INFO] layer; id=019c28ae-f9fa-7ad0-b4b8-4c60b26bdea9
12:44:41 [INFO] layer; id=019c28ae-f9fa-7ad0-b4b8-4c60b26bdea9
12:44:41 [INFO] layer; id=019c28ae-f9fa-7ad0-b4b8-4c60b26bdea9
12:44:41 [INFO] layer; id=019c28ae-f9fa-7ad0-b4b8-4c60b26bdea9
12:44:41 [INFO] Listening on 127.0.0.1:3000
12:44:41 [INFO] Received new jwk_set containing 2 keys.
12:44:47 [INFO] Access token expired for session 019c28ac-2f5a-75e1-b909-f862e773a5c6, attempting refresh
12:44:47 [INFO] Successfully refreshed access token for session 019c28ac-2f5a-75e1-b909-f862e773a5c6
13:05:19 [INFO] Callback called
13:05:19 [INFO] Successfully created session 019c28c1-db02-7261-aa2e-a2f0f7485080 for user
13:05:19 [INFO] Token scopes: EmptyExtraTokenFields
13:05:19 [INFO] Access token still valid for session 019c28c1-db02-7261-aa2e-a2f0f7485080 (expires in 60 seconds)
13:06:09 [INFO] Callback called
13:06:09 [INFO] Successfully created session 019c28c2-a14b-7e13-8593-7181dc374eca for user
13:06:09 [INFO] Token scopes: EmptyExtraTokenFields
13:06:11 [INFO] Callback called
13:06:11 [INFO] Successfully created session 019c28c2-a76f-7562-a2b5-197e2d42dbce for user
13:06:11 [INFO] Token scopes: EmptyExtraTokenFields
13:06:13 [INFO] Callback called
13:06:13 [INFO] Successfully created session 019c28c2-aed9-7932-b206-a19549ec754d for user
13:06:13 [INFO] Token scopes: EmptyExtraTokenFields
13:06:23 [INFO] Callback called
13:06:23 [INFO] Successfully created session 019c28c2-d540-7271-b0aa-dc302a522d1b for user
13:06:23 [INFO] Token scopes: EmptyExtraTokenFields
13:06:23 [INFO] Access token still valid for session 019c28c2-d540-7271-b0aa-dc302a522d1b (expires in 60 seconds)
13:07:25 [INFO] Callback called
13:07:25 [INFO] Successfully created session 019c28c3-ca65-7020-afe3-3adbaf4d57f9 for user
13:07:25 [INFO] Token scopes: EmptyExtraTokenFields
13:07:26 [INFO] Access token still valid for session 019c28c3-ca65-7020-afe3-3adbaf4d57f9 (expires in 59 seconds)
13:08:11 [INFO] Callback called
13:08:11 [INFO] Successfully created session 019c28c4-7db5-7ea3-be74-f77bf07e7f09 for user
13:08:11 [INFO] Token scopes: EmptyExtraTokenFields
13:08:12 [INFO] Access token still valid for session 019c28c4-7db5-7ea3-be74-f77bf07e7f09 (expires in 59 seconds)

View File

@ -0,0 +1,102 @@
13:08:37 [INFO] Redirect URI: http://127.0.0.1:3000/callback
13:08:37 [INFO] Logging initialized
13:08:37 [INFO] Starting Gas Delivery Backend
13:08:37 [INFO] Initializing redis server
13:08:37 [INFO] Starting axum server
13:08:37 [INFO] perform_oidc_discovery; kc_instance_id=019c28c4-e3be-7b41-8e29-eeee14d213d3 kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
13:08:37 [INFO] Starting OIDC discovery.
13:08:37 [INFO] layer; id=019c28c4-e3be-7b41-8e29-eef7556770a3
13:08:37 [INFO] layer; id=019c28c4-e3be-7b41-8e29-eef7556770a3
13:08:37 [INFO] layer; id=019c28c4-e3be-7b41-8e29-eef7556770a3
13:08:37 [INFO] layer; id=019c28c4-e3be-7b41-8e29-eef7556770a3
13:08:37 [INFO] layer; id=019c28c4-e3be-7b41-8e29-eef7556770a3
13:08:37 [INFO] layer; id=019c28c4-e3be-7b41-8e29-eef7556770a3
13:08:37 [INFO] Listening on 127.0.0.1:3000
13:08:38 [INFO] Received new jwk_set containing 2 keys.
13:09:19 [INFO] Callback called
13:09:19 [INFO] Successfully created session 019c28c5-8655-74d1-9e57-2a132b1213cb for user
13:09:19 [INFO] Token scopes: EmptyExtraTokenFields
13:09:19 [INFO] Access token still valid for session 019c28c5-8655-74d1-9e57-2a132b1213cb (expires in 60 seconds)
13:09:22 [INFO] Access token still valid for session 019c28c5-8655-74d1-9e57-2a132b1213cb (expires in 57 seconds)
13:09:29 [INFO] Access token still valid for session 019c28c5-8655-74d1-9e57-2a132b1213cb (expires in 50 seconds)
13:09:43 [INFO] Access token still valid for session 019c28c5-8655-74d1-9e57-2a132b1213cb (expires in 36 seconds)
13:10:09 [INFO] Callback called
13:10:09 [INFO] Successfully created session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 for user
13:10:09 [INFO] Token scopes: EmptyExtraTokenFields
13:10:10 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 59 seconds)
13:10:16 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 53 seconds)
13:16:18 [INFO] Access token expired for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959, attempting refresh
13:16:18 [INFO] Successfully refreshed access token for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959
13:16:21 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 57 seconds)
13:16:24 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 54 seconds)
13:16:31 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 47 seconds)
13:16:34 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 44 seconds)
13:16:38 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 40 seconds)
13:16:59 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 19 seconds)
13:17:01 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 17 seconds)
13:17:04 [INFO] Access token still valid for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959 (expires in 14 seconds)
14:02:29 [INFO] Access token expired for session 019c28c6-4b21-7541-b1ed-2ccc25ebe959, attempting refresh
14:02:29 [ERROR] Failed to refresh access token: Token refresh request failed: ServerResponse(StandardErrorResponse { error: invalid_grant, error_description: Some("Token is not active"), error_uri: None })
14:02:33 [WARN] Session not found in Redis: 019c28c6-4b21-7541-b1ed-2ccc25ebe959
14:02:39 [WARN] Session not found in Redis: 019c28c6-4b21-7541-b1ed-2ccc25ebe959
14:03:01 [WARN] Session not found in Redis: 019c28c6-4b21-7541-b1ed-2ccc25ebe959
14:03:05 [WARN] Session not found in Redis: 019c28c6-4b21-7541-b1ed-2ccc25ebe959
14:03:38 [INFO] Callback called
14:03:38 [INFO] Successfully created session 019c28f7-400a-7d22-8f5f-321b46c36a96 for user
14:03:38 [INFO] Token scopes: EmptyExtraTokenFields
14:03:38 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 60 seconds)
14:03:43 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 55 seconds)
14:03:49 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 49 seconds)
14:04:02 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 36 seconds)
14:04:05 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 33 seconds)
14:04:23 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 15 seconds)
14:04:29 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 9 seconds)
14:08:16 [INFO] Access token expired for session 019c28f7-400a-7d22-8f5f-321b46c36a96, attempting refresh
14:08:16 [INFO] Successfully refreshed access token for session 019c28f7-400a-7d22-8f5f-321b46c36a96
14:08:31 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 45 seconds)
14:08:35 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 41 seconds)
14:09:35 [INFO] Access token expired for session 019c28f7-400a-7d22-8f5f-321b46c36a96, attempting refresh
14:09:35 [INFO] Successfully refreshed access token for session 019c28f7-400a-7d22-8f5f-321b46c36a96
14:19:40 [INFO] Access token expired for session 019c28f7-400a-7d22-8f5f-321b46c36a96, attempting refresh
14:19:40 [INFO] Successfully refreshed access token for session 019c28f7-400a-7d22-8f5f-321b46c36a96
14:19:43 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 57 seconds)
14:19:46 [INFO] Access token still valid for session 019c28f7-400a-7d22-8f5f-321b46c36a96 (expires in 54 seconds)
14:21:54 [INFO] Access token expired for session 019c28f7-400a-7d22-8f5f-321b46c36a96, attempting refresh
14:21:54 [INFO] Successfully refreshed access token for session 019c28f7-400a-7d22-8f5f-321b46c36a96
14:23:04 [INFO] Callback called
14:23:04 [INFO] Successfully created session 019c2909-0a23-7910-b47d-229cb87792e1 for user
14:23:04 [INFO] Token scopes: EmptyExtraTokenFields
14:23:04 [INFO] Access token still valid for session 019c2909-0a23-7910-b47d-229cb87792e1 (expires in 60 seconds)
14:23:07 [INFO] Access token still valid for session 019c2909-0a23-7910-b47d-229cb87792e1 (expires in 57 seconds)
14:23:50 [INFO] Access token still valid for session 019c2909-0a23-7910-b47d-229cb87792e1 (expires in 14 seconds)
14:26:00 [INFO] Access token expired for session 019c2909-0a23-7910-b47d-229cb87792e1, attempting refresh
14:26:00 [INFO] Successfully refreshed access token for session 019c2909-0a23-7910-b47d-229cb87792e1
14:26:28 [INFO] Access token still valid for session 019c2909-0a23-7910-b47d-229cb87792e1 (expires in 32 seconds)
14:26:33 [INFO] Access token still valid for session 019c2909-0a23-7910-b47d-229cb87792e1 (expires in 27 seconds)
14:27:39 [INFO] Access token expired for session 019c2909-0a23-7910-b47d-229cb87792e1, attempting refresh
14:27:39 [INFO] Successfully refreshed access token for session 019c2909-0a23-7910-b47d-229cb87792e1
14:30:55 [INFO] Access token expired for session 019c2909-0a23-7910-b47d-229cb87792e1, attempting refresh
14:30:55 [INFO] Successfully refreshed access token for session 019c2909-0a23-7910-b47d-229cb87792e1
14:33:12 [INFO] Access token expired for session 019c2909-0a23-7910-b47d-229cb87792e1, attempting refresh
14:33:12 [INFO] Successfully refreshed access token for session 019c2909-0a23-7910-b47d-229cb87792e1
14:34:59 [INFO] Access token expired for session 019c2909-0a23-7910-b47d-229cb87792e1, attempting refresh
14:34:59 [INFO] Successfully refreshed access token for session 019c2909-0a23-7910-b47d-229cb87792e1
14:35:09 [INFO] Access token still valid for session 019c2909-0a23-7910-b47d-229cb87792e1 (expires in 50 seconds)
14:36:19 [INFO] Access token expired for session 019c2909-0a23-7910-b47d-229cb87792e1, attempting refresh
14:36:19 [INFO] Successfully refreshed access token for session 019c2909-0a23-7910-b47d-229cb87792e1
14:36:25 [INFO] Callback called
14:36:25 [INFO] Successfully created session 019c2915-43ab-77f0-8422-09f282747e59 for user
14:36:25 [INFO] Token scopes: EmptyExtraTokenFields
14:36:25 [INFO] Access token still valid for session 019c2915-43ab-77f0-8422-09f282747e59 (expires in 60 seconds)
14:36:27 [INFO] Access token still valid for session 019c2915-43ab-77f0-8422-09f282747e59 (expires in 58 seconds)
14:38:28 [INFO] Callback called
14:38:28 [INFO] Successfully created session 019c2917-2370-7f31-8a28-cec2911cc86d for user
14:38:28 [INFO] Token scopes: EmptyExtraTokenFields
14:38:28 [INFO] Access token still valid for session 019c2917-2370-7f31-8a28-cec2911cc86d (expires in 60 seconds)
14:38:30 [INFO] Access token still valid for session 019c2917-2370-7f31-8a28-cec2911cc86d (expires in 58 seconds)
14:39:35 [INFO] Callback called
14:39:35 [INFO] Successfully created session 019c2918-2b55-7002-8e53-12a8a5a0fdff for user
14:39:35 [INFO] Token scopes: EmptyExtraTokenFields
14:39:35 [INFO] Access token still valid for session 019c2918-2b55-7002-8e53-12a8a5a0fdff (expires in 60 seconds)
14:39:38 [INFO] Access token still valid for session 019c2918-2b55-7002-8e53-12a8a5a0fdff (expires in 57 seconds)
14:40:30 [INFO] Access token still valid for session 019c2918-2b55-7002-8e53-12a8a5a0fdff (expires in 5 seconds)

View File

@ -0,0 +1,48 @@
14:41:23 [INFO] Redirect URI: http://127.0.0.1:3000/callback
14:41:23 [INFO] Logging initialized
14:41:23 [INFO] Starting Gas Delivery Backend
14:41:23 [INFO] Initializing redis server
14:41:23 [INFO] Starting axum server
14:41:23 [INFO] perform_oidc_discovery; kc_instance_id=019c2919-d1fa-7543-bd68-04be3e5bb6f5 kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
14:41:23 [INFO] Starting OIDC discovery.
14:41:23 [INFO] layer; id=019c2919-d1fb-7cd1-8431-b566c09b9235
14:41:23 [INFO] layer; id=019c2919-d1fb-7cd1-8431-b566c09b9235
14:41:23 [INFO] layer; id=019c2919-d1fb-7cd1-8431-b566c09b9235
14:41:23 [INFO] layer; id=019c2919-d1fb-7cd1-8431-b566c09b9235
14:41:23 [INFO] layer; id=019c2919-d1fb-7cd1-8431-b566c09b9235
14:41:23 [INFO] layer; id=019c2919-d1fb-7cd1-8431-b566c09b9235
14:41:23 [INFO] Listening on 127.0.0.1:3000
14:41:24 [INFO] Received new jwk_set containing 2 keys.
14:41:30 [INFO] Access token expired for session 019c2918-2b55-7002-8e53-12a8a5a0fdff, attempting refresh
14:41:30 [INFO] Successfully refreshed access token for session 019c2918-2b55-7002-8e53-12a8a5a0fdff
14:50:01 [INFO] Access token expired for session 019c2918-2b55-7002-8e53-12a8a5a0fdff, attempting refresh
14:50:01 [INFO] Successfully refreshed access token for session 019c2918-2b55-7002-8e53-12a8a5a0fdff
15:03:17 [INFO] Access token expired for session 019c2918-2b55-7002-8e53-12a8a5a0fdff, attempting refresh
15:03:17 [INFO] Successfully refreshed access token for session 019c2918-2b55-7002-8e53-12a8a5a0fdff
15:32:24 [INFO] Access token expired for session 019c2918-2b55-7002-8e53-12a8a5a0fdff, attempting refresh
15:32:25 [INFO] Successfully refreshed access token for session 019c2918-2b55-7002-8e53-12a8a5a0fdff
15:34:19 [INFO] Callback called
15:34:19 [INFO] Successfully created session 019c294a-4604-7e01-bc3a-aee67bb365fb for user
15:34:19 [INFO] Token scopes: EmptyExtraTokenFields
15:34:19 [INFO] Access token still valid for session 019c294a-4604-7e01-bc3a-aee67bb365fb (expires in 60 seconds)
15:36:08 [INFO] Callback called
15:36:08 [INFO] Successfully created session 019c294b-eee5-72e1-a80e-b7e03b2e5877 for user
15:36:08 [INFO] Token scopes: EmptyExtraTokenFields
15:36:08 [INFO] Access token still valid for session 019c294b-eee5-72e1-a80e-b7e03b2e5877 (expires in 60 seconds)
15:36:53 [INFO] Callback called
15:36:53 [INFO] Successfully created session 019c294c-a034-7420-9a1e-8351aa3b457c for user
15:36:53 [INFO] Token scopes: EmptyExtraTokenFields
15:36:53 [INFO] Access token still valid for session 019c294c-a034-7420-9a1e-8351aa3b457c (expires in 60 seconds)
15:37:02 [INFO] Callback called
15:37:02 [INFO] Successfully created session 019c294c-c1a7-7402-9415-f0bd5b86b032 for user
15:37:02 [INFO] Token scopes: EmptyExtraTokenFields
15:37:02 [INFO] Access token still valid for session 019c294c-c1a7-7402-9415-f0bd5b86b032 (expires in 60 seconds)
15:37:43 [INFO] Callback called
15:37:43 [INFO] Successfully created session 019c294d-6384-7c03-af9f-000ce003bff6 for user
15:37:43 [INFO] Token scopes: EmptyExtraTokenFields
15:37:43 [INFO] Access token still valid for session 019c294d-6384-7c03-af9f-000ce003bff6 (expires in 60 seconds)
15:39:07 [INFO] Callback called
15:39:07 [INFO] Successfully created session 019c294e-aa9e-70e1-b059-38e2297ca892 for user
15:39:07 [INFO] Token scopes: EmptyExtraTokenFields
15:39:07 [INFO] Access token still valid for session 019c294e-aa9e-70e1-b059-38e2297ca892 (expires in 60 seconds)
15:39:09 [INFO] Access token still valid for session 019c294e-aa9e-70e1-b059-38e2297ca892 (expires in 58 seconds)

View File

@ -0,0 +1,89 @@
15:40:54 [INFO] Redirect URI: http://127.0.0.1:3000/callback
15:40:54 [INFO] Logging initialized
15:40:54 [INFO] Starting Gas Delivery Backend
15:40:54 [INFO] Initializing redis server
15:40:54 [INFO] Starting axum server
15:40:54 [INFO] perform_oidc_discovery; kc_instance_id=019c2950-4e1c-76f0-b16d-3eec0bc754be kc_server="http://127.0.0.1:8080/" kc_realm="master" oidc_discovery_endpoint="http://127.0.0.1:8080/realms/master/.well-known/openid-configuration"
15:40:54 [INFO] Starting OIDC discovery.
15:40:54 [INFO] layer; id=019c2950-4e1c-76f0-b16d-3ef3197d32e0
15:40:54 [INFO] layer; id=019c2950-4e1c-76f0-b16d-3ef3197d32e0
15:40:54 [INFO] layer; id=019c2950-4e1c-76f0-b16d-3ef3197d32e0
15:40:54 [INFO] layer; id=019c2950-4e1c-76f0-b16d-3ef3197d32e0
15:40:54 [INFO] layer; id=019c2950-4e1c-76f0-b16d-3ef3197d32e0
15:40:54 [INFO] layer; id=019c2950-4e1c-76f0-b16d-3ef3197d32e0
15:40:54 [INFO] Listening on 127.0.0.1:3000
15:40:54 [INFO] Received new jwk_set containing 2 keys.
15:41:03 [INFO] Callback called
15:41:03 [INFO] Successfully created session 019c2950-70b1-7b10-8a55-3c9ada4d8864 for user
15:41:03 [INFO] Token scopes: EmptyExtraTokenFields
15:41:03 [INFO] Access token still valid for session 019c2950-70b1-7b10-8a55-3c9ada4d8864 (expires in 60 seconds)
15:41:05 [INFO] Access token still valid for session 019c2950-70b1-7b10-8a55-3c9ada4d8864 (expires in 58 seconds)
15:41:46 [INFO] Access token still valid for session 019c2950-70b1-7b10-8a55-3c9ada4d8864 (expires in 17 seconds)
15:46:12 [INFO] Access token expired for session 019c2950-70b1-7b10-8a55-3c9ada4d8864, attempting refresh
15:46:12 [INFO] Successfully refreshed access token for session 019c2950-70b1-7b10-8a55-3c9ada4d8864
15:46:23 [INFO] Access token still valid for session 019c2950-70b1-7b10-8a55-3c9ada4d8864 (expires in 49 seconds)
15:46:39 [INFO] Access token still valid for session 019c2950-70b1-7b10-8a55-3c9ada4d8864 (expires in 33 seconds)
15:47:57 [INFO] Access token expired for session 019c2950-70b1-7b10-8a55-3c9ada4d8864, attempting refresh
15:47:57 [INFO] Successfully refreshed access token for session 019c2950-70b1-7b10-8a55-3c9ada4d8864
15:58:54 [INFO] Callback called
15:58:54 [INFO] Successfully created session 019c2960-c909-7ad2-aa9a-e6286fb8a38d for user
15:58:54 [INFO] Token scopes: EmptyExtraTokenFields
15:58:54 [INFO] Access token still valid for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d (expires in 60 seconds)
15:58:57 [INFO] Access token still valid for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d (expires in 57 seconds)
16:03:13 [INFO] Access token expired for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d, attempting refresh
16:03:13 [INFO] Successfully refreshed access token for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d
16:03:19 [INFO] Access token still valid for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d (expires in 54 seconds)
16:04:56 [INFO] Access token expired for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d, attempting refresh
16:04:56 [INFO] Successfully refreshed access token for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d
16:14:40 [INFO] Access token expired for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d, attempting refresh
16:14:40 [INFO] Successfully refreshed access token for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d
16:32:07 [INFO] Access token expired for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d, attempting refresh
16:32:07 [INFO] Successfully refreshed access token for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d
16:34:48 [INFO] Access token expired for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d, attempting refresh
16:34:48 [INFO] Successfully refreshed access token for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d
16:46:07 [INFO] Access token expired for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d, attempting refresh
16:46:07 [INFO] Successfully refreshed access token for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d
16:46:28 [INFO] Access token still valid for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d (expires in 39 seconds)
16:46:34 [INFO] Access token still valid for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d (expires in 33 seconds)
16:46:54 [INFO] Access token still valid for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d (expires in 13 seconds)
16:47:46 [INFO] Access token expired for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d, attempting refresh
16:47:46 [INFO] Successfully refreshed access token for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d
16:48:50 [INFO] Access token expired for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d, attempting refresh
16:48:50 [INFO] Successfully refreshed access token for session 019c2960-c909-7ad2-aa9a-e6286fb8a38d
16:53:28 [INFO] Callback called
16:53:28 [INFO] Successfully created session 019c2992-bbbe-7a31-9c39-d24a9af89e59 for user
16:53:28 [INFO] Token scopes: EmptyExtraTokenFields
16:53:28 [INFO] Access token still valid for session 019c2992-bbbe-7a31-9c39-d24a9af89e59 (expires in 60 seconds)
16:53:31 [INFO] Access token still valid for session 019c2992-bbbe-7a31-9c39-d24a9af89e59 (expires in 57 seconds)
16:55:06 [INFO] Access token expired for session 019c2992-bbbe-7a31-9c39-d24a9af89e59, attempting refresh
16:55:06 [INFO] Successfully refreshed access token for session 019c2992-bbbe-7a31-9c39-d24a9af89e59
16:55:41 [INFO] Access token still valid for session 019c2992-bbbe-7a31-9c39-d24a9af89e59 (expires in 25 seconds)
16:56:19 [INFO] Access token expired for session 019c2992-bbbe-7a31-9c39-d24a9af89e59, attempting refresh
16:56:19 [INFO] Successfully refreshed access token for session 019c2992-bbbe-7a31-9c39-d24a9af89e59
16:56:27 [INFO] Access token still valid for session 019c2992-bbbe-7a31-9c39-d24a9af89e59 (expires in 52 seconds)
16:57:59 [INFO] Access token expired for session 019c2992-bbbe-7a31-9c39-d24a9af89e59, attempting refresh
16:57:59 [INFO] Successfully refreshed access token for session 019c2992-bbbe-7a31-9c39-d24a9af89e59
17:00:14 [INFO] Access token expired for session 019c2992-bbbe-7a31-9c39-d24a9af89e59, attempting refresh
17:00:14 [INFO] Successfully refreshed access token for session 019c2992-bbbe-7a31-9c39-d24a9af89e59
17:02:17 [INFO] Access token expired for session 019c2992-bbbe-7a31-9c39-d24a9af89e59, attempting refresh
17:02:17 [INFO] Successfully refreshed access token for session 019c2992-bbbe-7a31-9c39-d24a9af89e59
17:04:48 [INFO] Access token expired for session 019c2992-bbbe-7a31-9c39-d24a9af89e59, attempting refresh
17:04:48 [INFO] Successfully refreshed access token for session 019c2992-bbbe-7a31-9c39-d24a9af89e59
17:07:53 [INFO] Access token expired for session 019c2992-bbbe-7a31-9c39-d24a9af89e59, attempting refresh
17:07:53 [INFO] Successfully refreshed access token for session 019c2992-bbbe-7a31-9c39-d24a9af89e59
17:08:18 [INFO] Callback called
17:08:18 [INFO] Successfully created session 019c29a0-50f3-7871-bf39-41d1657b14cc for user
17:08:18 [INFO] Token scopes: EmptyExtraTokenFields
17:08:18 [INFO] Access token still valid for session 019c29a0-50f3-7871-bf39-41d1657b14cc (expires in 60 seconds)
17:08:20 [INFO] Access token still valid for session 019c29a0-50f3-7871-bf39-41d1657b14cc (expires in 58 seconds)
17:08:22 [INFO] Access token still valid for session 019c29a0-50f3-7871-bf39-41d1657b14cc (expires in 56 seconds)
18:56:26 [INFO] Access token expired for session 019c29a0-50f3-7871-bf39-41d1657b14cc, attempting refresh
18:56:26 [ERROR] Failed to refresh access token: Token refresh request failed: ServerResponse(StandardErrorResponse { error: invalid_grant, error_description: Some("Token is not active"), error_uri: None })
19:41:28 [WARN] Session not found in Redis: 019c29a0-50f3-7871-bf39-41d1657b14cc
19:41:45 [INFO] Callback called
19:41:45 [INFO] Successfully created session 019c2a2c-cfde-70a3-98ae-861a5a55cd64 for user
19:41:45 [INFO] Token scopes: EmptyExtraTokenFields
19:41:46 [INFO] Access token still valid for session 019c2a2c-cfde-70a3-98ae-861a5a55cd64 (expires in 59 seconds)
19:41:51 [INFO] Access token still valid for session 019c2a2c-cfde-70a3-98ae-861a5a55cd64 (expires in 54 seconds)
19:54:01 [INFO] Access token expired for session 019c2a2c-cfde-70a3-98ae-861a5a55cd64, attempting refresh
19:54:01 [INFO] Successfully refreshed access token for session 019c2a2c-cfde-70a3-98ae-861a5a55cd64

77
docker-compose.yaml Normal file
View File

@ -0,0 +1,77 @@
services:
redis:
image: redis:7-alpine
container_name: redis-server
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
# microservice:
# build:
# context: .
# dockerfile: Dockerfile
# container_name: rust-microservice
# ports:
# - "3000:3000"
# environment:
# - REDIS_URL=redis://redis:6379
# - RUST_LOG=info
# depends_on:
# redis:
# condition: service_healthy
# networks:
# - app-network
# restart: unless-stopped
keycloak_web:
image: quay.io/keycloak/keycloak:23.0.7
container_name: keycloak_web
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://keycloakdb:5432/keycloak
KC_DB_USERNAME: ${POSTGRES_USER}
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
KC_HOSTNAME: 127.0.0.1
KC_HOSTNAME_PORT: 8080
KC_HOSTNAME_STRICT: false
KC_HOSTNAME_STRICT_HTTPS: false
KC_LOG_LEVEL: info
KC_METRICS_ENABLED: true
KC_HEALTH_ENABLED: true
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
command: start-dev
depends_on:
- keycloakdb
ports:
- 8080:8080
keycloakdb:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
networks:
app-network:
driver: bridge
volumes:
redis-data:
driver: local
postgres_data:
driver: local

View File

@ -0,0 +1,2 @@
GET http://127.0.0.1:3000/cars
Cookie: session_id=019c28ac-2f5a-75e1-b909-f862e773a5c6

View File

@ -0,0 +1,2 @@
GET http://127.0.0.1:3000/tour/1234
Cookie: session_id=019c28ac-2f5a-75e1-b909-f862e773a5c6

View File

@ -0,0 +1,5 @@
{
"dev": {
"supplier_id": "12345"
}
}

4
requests/userinfo.http Normal file
View File

@ -0,0 +1,4 @@
### GET request to example server
GET http://127.0.0.1:3000/userinfo
Cookie: session_id=019c22f6-d203-7ff2-b80c-c13ce9b84b69
###

16
src/api.rs Normal file
View File

@ -0,0 +1,16 @@
pub(crate) mod supplier;
pub(crate) mod tour;
use crate::util::{decode_payload_unchecked};
use axum::body::Body;
use axum::extract::Request;
use axum::response::IntoResponse;
use crate::model::{User};
pub async fn userinfo(request: Request<Body>) -> impl IntoResponse {
let access_token_string = &request.headers().get("authorization").unwrap().to_str().unwrap().to_string()[7..];
println!("access_token_string is {}", access_token_string);
let user = decode_payload_unchecked::<User>(access_token_string).unwrap();
serde_json::to_string(&user.employee).unwrap().into_response()
}

27
src/api/supplier.rs Normal file
View File

@ -0,0 +1,27 @@
use crate::dto::{GetCarInfosDTO, get_example_deliveries};
use crate::model::User;
use crate::response::{FailResponse, ResponseFactory};
use crate::util::decode_payload_unchecked;
use axum::Json;
use axum::http::StatusCode;
use axum_extra::TypedHeader;
use axum_extra::headers::Authorization;
use axum_extra::headers::authorization::Bearer;
pub async fn load_supplier_cars(
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
) -> Result<Json<GetCarInfosDTO>, (StatusCode, Json<FailResponse>)> {
let user_res = decode_payload_unchecked::<User>(auth.token());
if let Err(e) = user_res {
return Err((
StatusCode::UNAUTHORIZED,
Json(ResponseFactory::error(
format!("An error occured: {}", e.to_string()),
Some(StatusCode::UNAUTHORIZED.as_u16() as u32),
)),
));
}
Ok(Json(get_example_deliveries()))
}

29
src/api/tour.rs Normal file
View File

@ -0,0 +1,29 @@
use axum::extract::Path;
use axum::http::StatusCode;
use axum::Json;
use axum_extra::headers::Authorization;
use axum_extra::headers::authorization::Bearer;
use axum_extra::TypedHeader;
use crate::dto::{get_example_deliveries, get_test_tour, GetCarInfosDTO, TourDTO};
use crate::model::User;
use crate::response::{FailResponse, ResponseFactory};
use crate::util::decode_payload_unchecked;
pub async fn load_tour(
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
Path(car_id): Path<u64>
) -> Result<Json<TourDTO>, (StatusCode, Json<FailResponse>)> {
let user_res = decode_payload_unchecked::<User>(auth.token());
if let Err(e) = user_res {
return Err((
StatusCode::UNAUTHORIZED,
Json(ResponseFactory::error(
format!("An error occured: {}", e.to_string()),
Some(StatusCode::UNAUTHORIZED.as_u16() as u32),
)),
));
}
Ok(Json(get_test_tour(car_id)))
}

425
src/auth.rs Normal file
View File

@ -0,0 +1,425 @@
use std::collections::HashMap;
use crate::config::Config;
use crate::middleware::AppState;
use crate::repository::RedisRepository;
use axum::http::{StatusCode, header};
use axum::response::{Html, Response};
use axum::{
Router,
extract::{Query, State},
response::{IntoResponse, Redirect},
routing::get,
};
use axum_extra::extract::CookieJar;
use oauth2::basic::{
BasicErrorResponse, BasicRevocationErrorResponse, BasicTokenIntrospectionResponse,
BasicTokenResponse,
};
use oauth2::{
AuthUrl, AuthorizationCode, Client, ClientId, ClientSecret, CsrfToken, EndpointNotSet,
EndpointSet, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, Scope, StandardRevocableToken,
TokenResponse, TokenUrl, basic::BasicClient,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use axum::routing::post;
use log::info;
pub type OAuthClient = Client<
BasicErrorResponse,
BasicTokenResponse,
BasicTokenIntrospectionResponse,
StandardRevocableToken,
BasicRevocationErrorResponse,
EndpointSet,
EndpointNotSet,
EndpointNotSet,
EndpointNotSet,
EndpointSet,
>;
pub fn router(state: Arc<AppState>) -> Router {
Router::new()
.route("/login", get(login))
.route("/callback", get(callback))
.route("/logout", post(logout))
.with_state(state)
}
async fn login(State(client): State<Arc<AppState>>) -> impl IntoResponse {
let cloned_client = client.clone();
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
let csrf_token = CsrfToken::new_random();
// Store the PKCE verifier in Redis with CSRF token as key
let redis_key = format!("pkce_verifier:{}", csrf_token.secret());
let verifier_secret = pkce_verifier.secret().to_string();
match cloned_client
.repository
.set_with_expiry(&redis_key, &verifier_secret, 600) // 10 minutes expiry
.await
{
Ok(_) => {
let (auth_url, _) = cloned_client
.oauth_client
.authorize_url(|| csrf_token)
.add_scope(Scope::new("openid".to_string()))
.add_scope(Scope::new("profile".to_string()))
.add_scope(Scope::new("email".to_string()))
.set_pkce_challenge(pkce_challenge)
.url();
Redirect::to(auth_url.as_str()).into_response()
}
Err(e) => {
log::error!("Failed to store PKCE verifier in Redis: {:?}", e);
(
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to initiate login",
)
.into_response()
}
}
}
#[derive(Deserialize)]
pub struct Callback {
code: String,
state: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct UserSession {
pub(crate) access_token: String,
pub(crate) refresh_token: String,
pub(crate) expires_at: i64,
}
async fn callback(
State(client): State<Arc<AppState>>,
Query(query): Query<Callback>,
) -> impl IntoResponse {
let http_client = reqwest::ClientBuilder::new()
.redirect(reqwest::redirect::Policy::none())
.build()
.expect("Client should build");
let cloned_state = client.clone();
log::info!("Callback called");
// Retrieve the PKCE verifier from Redis using CSRF token
let redis_key = format!("pkce_verifier:{}", query.state);
let verifier_secret = match cloned_state.repository.get(&redis_key).await {
Ok(Some(secret)) => secret,
Ok(None) => {
log::error!("PKCE verifier not found for state: {}", query.state);
return (
StatusCode::BAD_REQUEST,
"Invalid or expired login session. Please try again.",
)
.into_response();
}
Err(e) => {
log::error!("Failed to retrieve PKCE verifier from Redis: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, "Login failed").into_response();
}
};
// Delete the verifier from Redis (one-time use)
let _ = cloned_state.repository.delete(&redis_key).await;
let pkce_verifier = PkceCodeVerifier::new(verifier_secret);
let token_result = cloned_state
.oauth_client
.exchange_code(AuthorizationCode::new(query.code))
.set_pkce_verifier(pkce_verifier)
.request_async(&http_client)
.await;
match token_result {
Ok(token) => {
let access_token = token.access_token().secret();
let refresh_token = token
.refresh_token()
.map(|rt| rt.secret().to_string())
.unwrap_or_else(|| "No refresh token".to_string());
let expires_at = chrono::Utc::now().timestamp()
+ token
.expires_in()
.map(|d| d.as_secs() as i64)
.unwrap_or(3600);
// ============================================
// 1. GENERATE A UNIQUE SESSION ID
// ============================================
let session_id = uuid::Uuid::now_v7().to_string();
// ============================================
// 2. CREATE THE USER SESSION STRUCT
// ============================================
let user_session = UserSession {
access_token: access_token.clone(),
refresh_token: refresh_token.clone(),
expires_at,
};
// ============================================
// 3. SERIALIZE THE SESSION TO JSON
// ============================================
let session_key = format!("user_session:{}", session_id);
let session_json = match serde_json::to_string(&user_session) {
Ok(json) => json,
Err(e) => {
log::error!("Failed to serialize user session: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, "Login failed").into_response();
}
};
// ============================================
// 4. STORE IN REDIS WITH 24 HOUR EXPIRATION
// This is where the tokens are actually stored!
// ============================================
if let Err(e) = cloned_state
.repository
.set_with_expiry(&session_key, &session_json, 86400) // 86400 = 24 hours
.await
{
log::error!("Failed to store user session in Redis: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, "Login failed").into_response();
}
info!("Successfully created session {} for user", session_id);
info!("Token scopes: {:?}", token.extra_fields());
// 4. Redirect to frontend
let redirect_url = format!("{}/?session_id={}", cloned_state.frontend_url.clone(), session_id);
Redirect::to(redirect_url.as_str()).into_response()
}
Err(e) => {
log::error!("Token exchange failed: {:?}", e);
(StatusCode::UNAUTHORIZED, format!("Login failed: {:?}", e)).into_response()
}
}
}
pub fn create_oauth_client(config: &Config) -> OAuthClient {
BasicClient::new(ClientId::new(config.keycloak.client_id.clone()))
.set_client_secret(ClientSecret::new(config.keycloak.client_secret.clone()))
.set_redirect_uri(RedirectUrl::new(config.keycloak.redirect_url.clone()).unwrap())
.set_token_uri(TokenUrl::new(config.keycloak.token_url.clone()).unwrap())
.set_auth_uri(AuthUrl::new(config.keycloak.auth_url.clone()).unwrap())
}
/// Internal helper to refresh access token
pub async fn refresh_access_token_internal(
client: &OAuthClient,
repository: &RedisRepository,
session_id: &str,
user_session: &mut UserSession,
) -> Result<String, String> {
use oauth2::{RefreshToken, TokenResponse};
let http_client = reqwest::ClientBuilder::new()
.redirect(reqwest::redirect::Policy::none())
.build()
.expect("Client should build");
let refresh_token = &user_session.refresh_token;
// Exchange refresh token for new access token
let token_result = client
.exchange_refresh_token(&RefreshToken::new(refresh_token.clone()))
.request_async(&http_client)
.await
.map_err(|e| format!("Token refresh request failed: {:?}", e))?;
// Update session with new tokens
let new_access_token = token_result.access_token().secret().to_string();
user_session.access_token = new_access_token.clone();
// Update refresh token if a new one was provided
if let Some(new_refresh_token) = token_result.refresh_token() {
user_session.refresh_token = new_refresh_token.secret().to_string();
}
// Update expiration time
user_session.expires_at = chrono::Utc::now().timestamp()
+ token_result
.expires_in()
.map(|d| d.as_secs() as i64)
.unwrap_or(3600);
// Save updated session back to Redis
let session_key = format!("user_session:{}", session_id);
let updated_json = serde_json::to_string(&user_session)
.map_err(|e| format!("Failed to serialize session: {:?}", e))?;
repository
.set_with_expiry(&session_key, &updated_json, 86400)
.await
.map_err(|e| format!("Failed to update session in Redis: {:?}", e))?;
Ok(new_access_token)
}
async fn logout(jar: CookieJar, State(oauth_state): State<Arc<AppState>>) -> impl IntoResponse {
// 1. Extract session ID from cookie
let session_id = match jar.get("session_id") {
Some(cookie) => cookie.value().to_string(),
None => {
log::warn!("Logout attempted without session cookie");
return (StatusCode::BAD_REQUEST, "No active session").into_response();
}
};
// 2. Get session from Redis to retrieve tokens
let session_key = format!("user_session:{}", session_id);
let session_json = match oauth_state.repository.get(&session_key).await {
Ok(Some(json)) => json,
Ok(None) => {
log::warn!("Session not found in Redis: {}", session_id);
// Session already gone, just clear cookie
return clear_session_cookie().into_response();
}
Err(e) => {
log::error!("Redis error while fetching session for logout: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, "Logout failed").into_response();
}
};
let user_session: UserSession = match serde_json::from_str(&session_json) {
Ok(session) => session,
Err(e) => {
log::error!("Failed to parse session JSON during logout: {:?}", e);
// Clean up anyway
let _ = oauth_state.repository.delete(&session_key).await;
return clear_session_cookie().into_response();
}
};
// 3. Revoke tokens at Keycloak
let revoke_result = revoke_tokens_at_keycloak(
&oauth_state,
&user_session.access_token,
&user_session.refresh_token,
)
.await;
if let Err(e) = revoke_result {
log::error!("Failed to revoke tokens at Keycloak: {}", e);
// Continue anyway - we'll still delete local session
} else {
log::info!(
"Successfully revoked tokens at Keycloak for session {}",
session_id
);
}
// 4. Delete session from Redis
match oauth_state.repository.delete(&session_key).await {
Ok(_) => {
log::info!("Successfully deleted session {} from Redis", session_id);
}
Err(e) => {
log::error!("Failed to delete session from Redis: {:?}", e);
}
}
// 5. Clear session cookie and respond
clear_session_cookie().into_response()
}
/// Helper function to revoke tokens at Keycloak's revocation endpoint
async fn revoke_tokens_at_keycloak(
oauth_state: &Arc<AppState>,
access_token: &str,
refresh_token: &str,
) -> Result<(), String> {
// Get client credentials from OAuth client
let client_id = oauth_state.oauth_client.client_id().as_str();
let client_secret = oauth_state.config.keycloak.client_secret.as_str();
// Build revocation endpoint URL
// Keycloak's revocation endpoint is typically at:
// {realm_url}/protocol/openid-connect/revoke
let token_url = oauth_state.config.keycloak.token_url.as_str();
// Replace /token with /revoke
let revoke_url = token_url.replace("/token", "/revoke");
log::info!("Revoking tokens at: {}", revoke_url);
let client = reqwest::Client::new();
// Revoke refresh token (this also invalidates the access token)
let revoke_refresh_result = client
.post(&revoke_url)
.form(&[
("token", refresh_token),
("token_type_hint", "refresh_token"),
("client_id", client_id),
("client_secret", client_secret),
])
.send()
.await
.map_err(|e| format!("Failed to send revoke request: {:?}", e))?;
if !revoke_refresh_result.status().is_success() {
let status = revoke_refresh_result.status();
let body = revoke_refresh_result
.text()
.await
.unwrap_or_else(|_| "Unable to read response".to_string());
log::warn!(
"Token revocation returned non-success status {}: {}",
status,
body
);
// Note: Keycloak returns 200 even if token is already invalid, so this is unusual
}
// Optionally also revoke access token explicitly
let revoke_access_result = client
.post(&revoke_url)
.form(&[
("token", access_token),
("token_type_hint", "access_token"),
("client_id", client_id),
("client_secret", client_secret),
])
.send()
.await
.map_err(|e| format!("Failed to send revoke request for access token: {:?}", e))?;
if !revoke_access_result.status().is_success() {
let status = revoke_access_result.status();
let body = revoke_access_result
.text()
.await
.unwrap_or_else(|_| "Unable to read response".to_string());
log::warn!(
"Access token revocation returned non-success status {}: {}",
status,
body
);
}
Ok(())
}
/// Helper function to create a response that clears the session cookie
fn clear_session_cookie() -> Response {
// Set cookie with Max-Age=0 to delete it
let clear_cookie = "session_id=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0";
Response::builder()
.status(StatusCode::OK)
.header(header::SET_COOKIE, clear_cookie)
.body("Logged out successfully".into())
.unwrap()
}

97
src/config.rs Normal file
View File

@ -0,0 +1,97 @@
use std::fs;
use std::path::PathBuf;
const CONFIG_FILE: &str = "config.toml";
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Config {
// Backend server configuration
pub log_file_prefix: String,
pub host_ip: String,
pub host_port: u16,
pub redis_url: String,
pub frontend_url: String,
// GSD RestAPI configuration
pub gsd_app_key: String,
pub gsd_rest_url: String,
pub gsd_user: String,
pub gsd_password: String,
pub gsd_app_names: Vec<String>,
pub keycloak: Keycloak,
}
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Keycloak {
pub realm_url: String,
pub client_id: String,
pub client_secret: String,
pub auth_url: String,
pub token_url: String,
pub redirect_url: String,
pub realm: String,
pub base_url: String,
}
impl Config {
pub fn get_host_url(&self) -> String {
format!("{}:{}", self.host_ip, self.host_port)
}
}
fn get_config_absolute_path() -> PathBuf {
PathBuf::from(CONFIG_FILE)
}
pub fn load_config() -> Result<Config, Box<dyn std::error::Error>> {
if fs::exists(get_config_absolute_path())? {
Ok(toml::from_str(&fs::read_to_string(CONFIG_FILE)?)?)
} else {
let config = create_standard_config();
save_config(&config)?;
Ok(config)
}
}
pub fn generate_log_file_name(prefix: String) -> String {
format!("{}_{}.log", prefix, chrono::Local::now().format("%Y-%m-%dT%H-%M-%S%.6f%z"))
}
pub fn save_config(config: &Config) -> Result<(), Box<dyn std::error::Error>> {
fs::write(get_config_absolute_path(), toml::to_string(config)?)?;
Ok(())
}
pub fn create_standard_config() -> Config {
Config {
log_file_prefix: String::from("delivery_backend"),
host_ip: String::from("127.0.0.1"),
host_port: 3000,
redis_url: String::from("redis://127.0.0.1:6379"),
gsd_rest_url: String::from("http://127.0.0.1:8334"),
gsd_app_key: String::from("GSD-RestApi"),
frontend_url: String::from("http://127.0.0.1:3000"),
gsd_app_names: vec![String::from("GSD-RestApi")],
gsd_user: String::from("<GSD-USER>"),
gsd_password: String::from("<GSD-Password>"),
keycloak: Keycloak {
realm_url: String::from("http://127.0.0.1:8080/auth/realms/master"),
client_id: String::from("delivery-backend"),
client_secret: String::from(""),
realm: String::from("master"),
base_url: String::from("http://127.0.0.1:8080"),
auth_url: String::from(
"http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth",
),
token_url: String::from(
"http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token",
),
redirect_url: String::from("http://127.0.0.1:3000/callback"),
},
}
}

331
src/dto.rs Normal file
View File

@ -0,0 +1,331 @@
use chrono::{DateTime, TimeZone, Utc};
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct CarInfoDTO {
pub amount_deliveries: u32,
pub car: CarDTO,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct CarDTO {
pub id: u32,
pub car_name: String,
pub driver_name: Option<String>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct GetCarInfosDTO {
pub deliveries: Vec<CarInfoDTO>,
pub date: String
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct ArticleDTO {
pub id: u64,
pub title: String,
pub quantity: u64,
pub price_per_quantity: f32,
pub deposit_price_per_quantity: f32,
pub quantity_delivered: u64,
pub quantity_returned: u64,
pub quantity_to_deposit: u64,
pub reference_to: Option<u64>
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct AddressDTO {
pub street: String,
pub housing_number: String,
pub postal_code: String,
pub city: String,
pub country: Option<String>
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct CustomerDTO {
pub name: String,
pub id: u64,
pub address: AddressDTO
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct ReceiptDTO {
pub articles: Vec<ArticleDTO>,
pub customer: CustomerDTO,
pub total_gross_price: f32,
pub total_net_price: f32
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct DeliveryDTO {
pub receipt: ReceiptDTO,
pub information_for_driver: String,
pub desired_delivery_time: DateTime<Utc>,
pub time_delivered: Option<DateTime<Utc>>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct TourDTO {
pub car: CarDTO,
pub date: String,
pub deliveries: Vec<DeliveryDTO>,
}
pub fn get_example_deliveries() -> GetCarInfosDTO {
GetCarInfosDTO {
date: format!("{}", Utc::now().format("%d.%m.%Y")),
deliveries: vec![
CarInfoDTO {
car: CarDTO {
id: 1,
car_name: "OG DN 1998".to_string(),
driver_name: Some("Dennis Nemec".to_string()),
},
amount_deliveries: 12,
},
CarInfoDTO {
car: CarDTO {
id: 2,
car_name: "S SM 2547".to_string(),
driver_name: None // "Sarah Müller".to_string(),
},
amount_deliveries: 8,
},
CarInfoDTO {
car: CarDTO {
id: 3,
car_name: "M MS 3891".to_string(),
driver_name: Some("Michael Schmidt".to_string()),
},
amount_deliveries: 15,
},
CarInfoDTO {
car: CarDTO {
id: 4,
car_name: "KA AW 1024".to_string(),
driver_name: Some("Anna Weber".to_string()),
},
amount_deliveries: 6,
},
CarInfoDTO {
car: CarDTO {
id: 5,
car_name: "FR TB 7652".to_string(),
driver_name: None // "Thomas Becker".to_string(),
},
amount_deliveries: 11,
},
CarInfoDTO {
car: CarDTO {
id: 6,
car_name: "HD JH 4389".to_string(),
driver_name: Some("Julia Hoffmann".to_string()),
},
amount_deliveries: 9,
},
CarInfoDTO {
car: CarDTO {
id: 7,
car_name: "KN MF 5123".to_string(),
driver_name: Some("Markus Fischer".to_string()),
},
amount_deliveries: 13,
},
],
}
}
pub fn get_test_tour(car_id: u64) -> TourDTO {
TourDTO {
car: CarDTO {
id: car_id as u32,
car_name: String::from("Mercedes Sprinter"),
driver_name: Some(String::from("Hans Müller")),
},
date: String::from("2026-02-05"),
deliveries: vec![
// Lieferung 1
DeliveryDTO {
receipt: ReceiptDTO {
articles: vec![
ArticleDTO {
id: 1001,
title: String::from("Propangas 5kg"),
quantity: 4,
price_per_quantity: 18.99,
deposit_price_per_quantity: 0.0,
quantity_delivered: 4,
quantity_returned: 0,
quantity_to_deposit: 0,
reference_to: None,
},
ArticleDTO {
id: 2001,
title: String::from("Pfandflasche 5kg"),
quantity: 4,
price_per_quantity: 0.0,
deposit_price_per_quantity: 35.00,
quantity_delivered: 4,
quantity_returned: 2,
quantity_to_deposit: 2,
reference_to: Some(1001),
},
ArticleDTO {
id: 1002,
title: String::from("Propangas 11kg"),
quantity: 2,
price_per_quantity: 32.99,
deposit_price_per_quantity: 0.0,
quantity_delivered: 2,
quantity_returned: 0,
quantity_to_deposit: 0,
reference_to: None,
},
ArticleDTO {
id: 2002,
title: String::from("Pfandflasche 11kg"),
quantity: 2,
price_per_quantity: 0.0,
deposit_price_per_quantity: 45.00,
quantity_delivered: 2,
quantity_returned: 1,
quantity_to_deposit: 1,
reference_to: Some(1002),
},
],
customer: CustomerDTO {
name: String::from("Grillhütte Waldheim"),
id: 5001,
address: AddressDTO {
street: String::from("Waldstraße"),
housing_number: String::from("15"),
postal_code: String::from("70597"),
city: String::from("Stuttgart"),
country: Some(String::from("Deutschland")),
},
},
total_gross_price: 211.94,
total_net_price: 178.10,
},
information_for_driver: String::from("Flaschen beim Lagerraum hinter dem Gebäude abstellen. Alte Flaschen abholen."),
desired_delivery_time: Utc.with_ymd_and_hms(2026, 2, 5, 8, 0, 0).unwrap(),
time_delivered: None // Utc.with_ymd_and_hms(2026, 2, 5, 8, 15, 0).unwrap(),
},
// Lieferung 2
DeliveryDTO {
receipt: ReceiptDTO {
articles: vec![
ArticleDTO {
id: 1001,
title: String::from("Propangas 5kg"),
quantity: 8,
price_per_quantity: 18.99,
deposit_price_per_quantity: 0.0,
quantity_delivered: 8,
quantity_returned: 0,
quantity_to_deposit: 0,
reference_to: None,
},
ArticleDTO {
id: 2001,
title: String::from("Pfandflasche 5kg"),
quantity: 8,
price_per_quantity: 0.0,
deposit_price_per_quantity: 35.00,
quantity_delivered: 8,
quantity_returned: 7,
quantity_to_deposit: 1,
reference_to: Some(1001),
},
ArticleDTO {
id: 1003,
title: String::from("Propangas 33kg"),
quantity: 1,
price_per_quantity: 89.99,
deposit_price_per_quantity: 0.0,
quantity_delivered: 1,
quantity_returned: 0,
quantity_to_deposit: 0,
reference_to: None,
},
ArticleDTO {
id: 2003,
title: String::from("Pfandflasche 33kg"),
quantity: 1,
price_per_quantity: 0.0,
deposit_price_per_quantity: 75.00,
quantity_delivered: 1,
quantity_returned: 1,
quantity_to_deposit: 0,
reference_to: Some(1003),
},
],
customer: CustomerDTO {
name: String::from("Campingplatz Sonnenhof"),
id: 5002,
address: AddressDTO {
street: String::from("Am See"),
housing_number: String::from("23"),
postal_code: String::from("70376"),
city: String::from("Stuttgart"),
country: Some(String::from("Deutschland")),
},
},
total_gross_price: 241.91,
total_net_price: 203.28,
},
information_for_driver: String::from("Rezeption informieren. Flaschen zur Gasflaschenhütte bei Stellplatz A12 bringen."),
desired_delivery_time: Utc.with_ymd_and_hms(2026, 2, 5, 10, 0, 0).unwrap(),
time_delivered: Some(Utc.with_ymd_and_hms(2026, 2, 5, 10, 20, 0).unwrap()),
},
// Lieferung 3
DeliveryDTO {
receipt: ReceiptDTO {
articles: vec![
ArticleDTO {
id: 1002,
title: String::from("Propangas 11kg"),
quantity: 6,
price_per_quantity: 32.99,
deposit_price_per_quantity: 0.0,
quantity_delivered: 6,
quantity_returned: 0,
quantity_to_deposit: 0,
reference_to: None,
},
ArticleDTO {
id: 2002,
title: String::from("Pfandflasche 11kg"),
quantity: 6,
price_per_quantity: 0.0,
deposit_price_per_quantity: 45.00,
quantity_delivered: 6,
quantity_returned: 5,
quantity_to_deposit: 1,
reference_to: Some(1002),
},
],
customer: CustomerDTO {
name: String::from("Baumarkt Schmidt GmbH"),
id: 5003,
address: AddressDTO {
street: String::from("Industriestraße"),
housing_number: String::from("88"),
postal_code: String::from("70565"),
city: String::from("Stuttgart"),
country: Some(String::from("Deutschland")),
},
},
total_gross_price: 242.94,
total_net_price: 204.15,
},
information_for_driver: String::from("Wareneingang nutzen, Anlieferung nur bis 12 Uhr möglich."),
desired_delivery_time: Utc.with_ymd_and_hms(2026, 2, 5, 11, 30, 0).unwrap(),
time_delivered: Some(Utc.with_ymd_and_hms(2026, 2, 5, 11, 45, 0).unwrap()),
},
],
}
}

2
src/gsd.rs Normal file
View File

@ -0,0 +1,2 @@
pub(crate) mod dto;
pub(crate) mod service;

27
src/gsd/dto.rs Normal file
View File

@ -0,0 +1,27 @@
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GSDLoginRequestDTO {
pub user: String,
pub pass: String,
pub app_names: Vec<String>,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GSDResponseDTO {
pub status: Option<GSDResponseStatusDTO>,
pub data: Option<GSDLoginResponseDataDTO>,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GSDLoginResponseDataDTO {
pub session_id: Option<String>,
}
#[derive(serde::Deserialize, serde::Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct GSDResponseStatusDTO {
pub internal_status: String,
pub status_message: String,
}

134
src/gsd/service.rs Normal file
View File

@ -0,0 +1,134 @@
use crate::config::Config;
use crate::gsd::dto::*;
use axum::body::Body;
use axum::extract::Request;
use log::{error, info};
use reqwest::Response;
#[derive(Clone)]
pub struct GSDService {
host_url: String,
app_names: Vec<String>,
app_key: String,
username: String,
password: String,
}
#[derive(Debug)]
pub enum GSDServiceError {
LoginFailed,
LoginResponseParsingFailed,
RequestError(String),
}
impl GSDService {
pub async fn get_session(&self) -> Result<String, GSDServiceError> {
info!(
"Session: No session found. Generate session from GSD server {}",
self.host_url
);
let dto = GSDLoginRequestDTO {
user: self.username.clone(),
pass: self.password.clone(),
app_names: self.app_names.clone(),
};
let response = reqwest::Client::new()
.post(format!("{}/v1/login", self.host_url.clone()))
.header("appKey", self.app_key.as_str())
.header("Content-Type", "application/json")
.json(&dto)
.send()
.await
.map_err(|e| {
error!("Session: error request to GSD: {}", e);
GSDServiceError::LoginFailed
})?;
let response_dto: GSDResponseDTO = response.json().await.map_err(|e| {
error!("Session: error request to GSD: {}", e);
GSDServiceError::LoginResponseParsingFailed
})?;
let response_dto_unwrapped = response_dto.status.unwrap();
if response_dto_unwrapped.internal_status != "0" {
error!(
"Session: error message from GSD: {}",
response_dto_unwrapped.status_message
);
Err(GSDServiceError::LoginFailed)
} else {
match response_dto.data {
Some(data) => {
info!(
"Session: successfully obtained session with session id {:?}",
&data.session_id.as_ref()
);
Ok(data.session_id.unwrap())
}
None => {
error!("Session: failed to obtain session id. No session id in request found.");
Err(GSDServiceError::LoginResponseParsingFailed)
}
}
}
}
pub async fn forward_post_request(
&self,
request: Request<Body>,
) -> Result<Response, GSDServiceError> {
let (parts, body) = request.into_parts();
reqwest::Client::new()
.post(format!("{}{}", self.host_url.clone(), parts.uri))
.headers(parts.headers)
.body(axum::body::to_bytes(body, usize::MAX).await.unwrap())
.send()
.await
.map_err(|e| GSDServiceError::RequestError(e.to_string()))
}
pub async fn forward_patch_request(
&self,
request: Request<Body>,
) -> Result<Response, GSDServiceError> {
let (parts, body) = request.into_parts();
reqwest::Client::new()
.patch(format!("{}{}", self.host_url.clone(), parts.uri))
.headers(parts.headers)
.body(axum::body::to_bytes(body, usize::MAX).await.unwrap())
.send()
.await
.map_err(|e| GSDServiceError::RequestError(e.to_string()))
}
pub async fn forward_get_request(
&self,
request: Request<Body>,
) -> Result<Response, GSDServiceError> {
let (parts, body) = request.into_parts();
reqwest::Client::new()
.get(format!("{}{}", self.host_url.clone(), parts.uri))
.headers(parts.headers)
.body(axum::body::to_bytes(body, usize::MAX).await.unwrap())
.send()
.await
.map_err(|e| GSDServiceError::RequestError(e.to_string()))
}
}
impl From<&Config> for GSDService {
fn from(config: &Config) -> Self {
Self {
host_url: config.gsd_rest_url.clone(),
app_names: config.gsd_app_names.clone(),
app_key: config.gsd_app_key.clone(),
username: config.gsd_user.clone(),
password: config.gsd_password.clone(),
}
}
}

84
src/main.rs Normal file
View File

@ -0,0 +1,84 @@
use crate::api::userinfo;
use crate::config::load_config;
use crate::middleware::AppState;
use crate::repository::RedisRepository;
use crate::util::initialize_logging;
use axum::routing::get;
use axum::{Extension, Router};
use axum_keycloak_auth::PassthroughMode;
use axum_keycloak_auth::instance::{KeycloakAuthInstance, KeycloakConfig};
use axum_keycloak_auth::layer::KeycloakAuthLayer;
use log::info;
use oauth2::url::Url;
use std::sync::Arc;
mod api;
mod auth;
mod config;
mod gsd;
mod middleware;
mod model;
mod repository;
mod util;
mod response;
mod dto;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = load_config()?;
initialize_logging(&config);
info!("Redirect URI: {}", config.keycloak.redirect_url);
info!("Logging initialized");
info!("Starting Gas Delivery Backend");
let redis_url = config.redis_url.clone();
let host_url = config.get_host_url().clone();
info!("Initializing redis server");
let state = Arc::new(AppState {
config: config.clone(),
repository: RedisRepository::try_new(redis_url).await?,
gsd_service: (&config).into(),
oauth_client: auth::create_oauth_client(&config),
frontend_url: config.frontend_url.clone(),
});
//
info!("Starting axum server");
let keycloak_instance: Arc<KeycloakAuthInstance> = Arc::new(KeycloakAuthInstance::new(
KeycloakConfig::builder()
.server(Url::parse(config.keycloak.base_url.as_str())?)
.realm(config.keycloak.realm)
.build(),
));
let auth_router = auth::router(state.clone());
let api_router = Router::new()
.route("/cars", get(api::supplier::load_supplier_cars))
.route("/tour/{car_id}", get(api::tour::load_tour))
.route("/userinfo", get(userinfo))
.route_layer(Extension(state.clone()))
.route_layer(
KeycloakAuthLayer::<String>::builder()
.instance(keycloak_instance.clone())
.passthrough_mode(PassthroughMode::Block)
.persist_raw_claims(false)
.expected_audiences(vec![String::from("account")])
.build(),
)
.route_layer(axum::middleware::from_fn_with_state(
state.clone(),
middleware::session_auth_middleware,
))
.with_state(state);
let app = Router::new().merge(api_router).merge(auth_router);
info!("Listening on {}", host_url);
let listener = tokio::net::TcpListener::bind(host_url.clone()).await?;
axum::serve(listener, app).await?;
Ok(())
}

168
src/middleware.rs Normal file
View File

@ -0,0 +1,168 @@
use crate::auth::{OAuthClient, UserSession, refresh_access_token_internal};
use crate::config::Config;
use crate::gsd::service::GSDService;
use crate::repository::RedisRepository;
use crate::util::set_and_log_session;
use axum::extract::{Request, State};
use axum::http::{HeaderValue, StatusCode};
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use axum_extra::extract::CookieJar;
use log::{error, info, warn};
use std::sync::Arc;
#[derive(Clone)]
pub struct AppState {
pub config: Config,
pub repository: RedisRepository,
pub gsd_service: GSDService,
pub oauth_client: OAuthClient,
pub frontend_url: String,
}
/// Middleware to validate session and refresh tokens if needed
pub async fn session_auth_middleware(
jar: CookieJar,
State(state): State<Arc<AppState>>,
mut request: Request,
next: Next,
) -> Response {
// 1. Extract session ID from cookie
let session_id = match jar.get("session_id") {
Some(cookie) => cookie.value().to_string(),
None => {
warn!("No session cookie found");
return (StatusCode::UNAUTHORIZED, "No session cookie").into_response();
}
};
// 2. Find session in Redis
let session_key = format!("user_session:{}", session_id);
let session_json = match state.repository.get(&session_key).await {
Ok(Some(json)) => json,
Ok(None) => {
warn!("Session not found in Redis: {}", session_id);
return (StatusCode::UNAUTHORIZED, "Session expired or invalid").into_response();
}
Err(e) => {
error!("Redis error while fetching session: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response();
}
};
// 3. Parse session data
let mut user_session: UserSession = match serde_json::from_str(&session_json) {
Ok(session) => session,
Err(e) => {
error!("Failed to parse session JSON: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, "Invalid session data").into_response();
}
};
// 4. Check if access token is expired
let now = chrono::Utc::now().timestamp();
if user_session.expires_at <= now {
info!(
"Access token expired for session {}, attempting refresh",
session_id
);
// 5. Refresh the access token using refresh token
match refresh_access_token_internal(
&state.oauth_client,
&state.repository,
&session_id,
&mut user_session,
)
.await
{
Ok(new_access_token) => {
info!(
"Successfully refreshed access token for session {}",
session_id
);
user_session.access_token = new_access_token;
}
Err(e) => {
error!("Failed to refresh access token: {}", e);
// Clean up invalid session
let _ = state.repository.delete(&session_key).await;
return (
StatusCode::UNAUTHORIZED,
"Session expired, please login again",
)
.into_response();
}
}
} else {
info!(
"Access token still valid for session {} (expires in {} seconds)",
session_id,
user_session.expires_at - now
);
}
// 6. Attach validated access token to request for downstream handlers
match HeaderValue::from_str(format!("Bearer {}", &user_session.access_token).as_str()) {
Ok(header_value) => {
request.headers_mut().insert("authorization", header_value);
}
Err(e) => {
error!("Failed to create authorization header: {:?}", e);
return (StatusCode::INTERNAL_SERVER_ERROR, "Internal error").into_response();
}
}
// 7. Pass the request to the next handler
next.run(request).await
}
pub async fn gsd_decorate_header(
State(state): State<Arc<AppState>>,
mut request: Request,
next: Next,
) -> Response {
let state_cloned = state.clone();
let session = state_cloned.repository.get_session().await;
match session {
Ok(session) => {
let session_value;
if session.is_none() {
match state_cloned.gsd_service.get_session().await {
Ok(session) => {
session_value = session.clone();
set_and_log_session(&state_cloned, session.clone()).await;
}
Err(error) => {
error!("Error getting session: {:?}", error);
return StatusCode::UNAUTHORIZED.into_response();
}
}
} else {
session_value = session.unwrap();
}
request.headers_mut().insert(
"sessionId",
HeaderValue::from_str(session_value.as_str()).unwrap(),
);
}
Err(error) => {
error!(
"Redis error occured during fetching current session id. Error: {}",
error
);
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
}
request.headers_mut().insert(
"appkey",
HeaderValue::from_str(state_cloned.config.gsd_app_key.as_str()).unwrap(),
);
next.run(request).await
}

13
src/model.rs Normal file
View File

@ -0,0 +1,13 @@
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct User {
pub employee: Employee,
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Employee {
pub last_name: String,
pub first_name: String,
pub mail: String,
pub supplier_id: u64
}

51
src/repository.rs Normal file
View File

@ -0,0 +1,51 @@
use redis::aio::ConnectionManager;
use redis::{AsyncTypedCommands, Connection, RedisError, RedisResult};
#[derive(Clone)]
pub struct RedisRepository {
connection_manager: ConnectionManager,
}
impl RedisRepository {
pub async fn try_new(redis_url: String) -> Result<RedisRepository, RedisError> {
Ok(RedisRepository {
connection_manager: redis::Client::open(redis_url)?
.get_connection_manager()
.await?,
})
}
pub async fn get_session(&self) -> RedisResult<Option<String>> {
self.connection_manager
.clone()
.get::<String>("current_session".to_string())
.await
}
pub async fn set_session(&self, session: String) -> RedisResult<()> {
self.connection_manager
.clone()
.set("current_session", session)
.await?;
Ok(())
}
pub async fn set_with_expiry(&self, key: &str, value: &str, expiry: u64) -> RedisResult<()> {
self.connection_manager
.clone()
.set_ex(key, value, expiry)
.await
}
pub async fn get(&self, key: &str) -> RedisResult<Option<String>> {
self.connection_manager
.clone()
.get::<String>(key.to_string())
.await
}
pub async fn delete(&self, key: &str) -> RedisResult<usize> {
self.connection_manager.clone().del(key.to_string()).await
}
}

20
src/response.rs Normal file
View File

@ -0,0 +1,20 @@
use axum::Json;
use axum::response::{IntoResponse, Response};
use serde::{Deserialize, Serialize};
use serde_json::json;
pub struct ResponseFactory {}
impl ResponseFactory {
pub fn error(message: String, code: Option<u32>) -> FailResponse {
FailResponse {
code,
message,
}
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct FailResponse {
pub code: Option<u32>,
pub message: String,
}

47
src/util.rs Normal file
View File

@ -0,0 +1,47 @@
use crate::config::{Config, generate_log_file_name};
use crate::middleware::AppState;
use log::{LevelFilter, error, info};
use simplelog::{ColorChoice, CombinedLogger, TermLogger, TerminalMode, WriteLogger};
use std::fs::File;
use std::sync::Arc;
use serde::de::DeserializeOwned;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
pub fn decode_payload_unchecked<T: DeserializeOwned>(token: &str) -> Result<T, Box<dyn std::error::Error>> {
let mut parts = token.split('.');
let _header = parts.next().ok_or("missing header")?;
let payload_b64 = parts.next().ok_or("missing payload")?;
// signature is parts.next() but we ignore it here
let payload = URL_SAFE_NO_PAD.decode(payload_b64.as_bytes())?;
let claims = serde_json::from_slice::<T>(&payload)?;
Ok(claims)
}
pub fn initialize_logging(config: &Config) {
CombinedLogger::init(vec![
TermLogger::new(
LevelFilter::Info,
simplelog::Config::default(),
TerminalMode::Mixed,
ColorChoice::Auto,
),
WriteLogger::new(
LevelFilter::Info,
simplelog::Config::default(),
File::create(generate_log_file_name(config.log_file_prefix.clone())).unwrap(),
),
])
.unwrap();
}
pub async fn set_and_log_session(state: &Arc<AppState>, session: String) {
match state.repository.set_session(session.clone()).await {
Ok(_) => {
info!("Redis: saved session {}", &session);
}
Err(err) => {
error!("Redis: failed to save session: {}", err);
}
}
}