package oidc import ( "context" "errors" "fmt" "gitea.perlbach24.de/scripte/nexarch/internal/auth" "gitea.perlbach24.de/scripte/nexarch/internal/ldapsync" "gitea.perlbach24.de/scripte/nexarch/internal/rbac" "gitea.perlbach24.de/scripte/nexarch/internal/user" ) // CompleteOIDCLogin verifiziert das ID-Token (Signatur, Ablauf, Aussteller, // Nonce — Akzeptanzkriterium 3), loest den State einmalig ein // (Akzeptanzkriterium/Pruefung 3), legt bei Bedarf einen lokalen Benutzer an // und mapped Rollen aus den OIDC-Claims ueber DIESELBE Erlaubnis-Matrix wie // IAM-05/LDAP (ldapsync.RoleMappingStore — kein zweiter, paralleler // Mapping-Mechanismus, Akzeptanzkriterium 2). Bei Erfolg wird ein normales // IAM-02-Sitzungs-Token ausgestellt. func CompleteOIDCLogin( ctx context.Context, verifier *Verifier, states *StateStore, users *user.TenantUserStore, roles *rbac.Store, roleMapping *ldapsync.RoleMappingStore, issuer *auth.TokenIssuer, tenantSlug, state, idToken string, ) (token string, err error) { nonce, err := states.Consume(ctx, state) if err != nil { return "", err } claims, err := verifier.VerifyWithNonce(idToken, nonce) if err != nil { return "", err } if claims.Email == "" { return "", fmt.Errorf("%w: id-token ohne e-mail-claim", ErrInvalidToken) } u, err := findOrCreateUser(ctx, users, claims.Email) if err != nil { return "", err } if role, ok, err := roleMapping.HighestRoleFor(ctx, claims.Roles); err != nil { return "", err } else if ok { if _, err := roles.Assign(ctx, u.ID, role, "oidc-sso"); err != nil { return "", err } } return issuer.Issue(u.ID, tenantSlug) } func findOrCreateUser(ctx context.Context, users *user.TenantUserStore, email string) (user.User, error) { creds, err := users.GetByEmailForAuth(ctx, email) if err == nil { return creds.User, nil } if !errors.Is(err, user.ErrNotFound) { return user.User{}, err } return users.Create(ctx, email, email) }