package auth import ( "context" "database/sql" "errors" "github.com/royalcat/konfa-server/pkg/uuid" "github.com/royalcat/konfa-server/src/store" "github.com/uptrace/bun" "github.com/zitadel/oidc/v3/pkg/client/rs" "github.com/zitadel/oidc/v3/pkg/oidc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var ( errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata") errInvalidToken = status.Errorf(codes.Unauthenticated, "invalid token") ) type AuthenticatorConfig struct { Issuer string ClientID string ClientSecret string } type Authenticator struct { provider rs.ResourceServer db *bun.DB } func NewAuthenticator(ctx context.Context, db *bun.DB, acfg AuthenticatorConfig) (*Authenticator, error) { provider, err := rs.NewResourceServerClientCredentials(ctx, acfg.Issuer, acfg.ClientID, acfg.ClientSecret) if err != nil { return nil, err } return &Authenticator{ provider: provider, db: db, }, nil } func (a *Authenticator) authorize(ctx context.Context, token string) (context.Context, error) { // var claims oidc.AccessTokenClaims // _, err := oidc.ParseToken(token, &claims) // if err != nil { // return nil, err // } resp, err := rs.Introspect[*oidc.IntrospectionResponse](ctx, a.provider, token) if err != nil { return nil, err } user, err := a.loginWithExternal(ctx, resp) if err != nil { return nil, err } ctx = context.WithValue(ctx, ctxUserKey, &user) return ctx, nil } func (a *Authenticator) loginWithExternal(ctx context.Context, resp *oidc.IntrospectionResponse) (store.User, error) { var user store.User err := a.db.NewSelect(). Model(&user). Join("JOIN external_login ON \"user\".\"id\" = \"external_login\".\"user_id\""). Where("issuer = ?", resp.Issuer). Where("subject = ?", resp.Subject). Scan(ctx) if err != nil { if errors.Is(err, sql.ErrNoRows) { return a.createUserFromExternal(ctx, resp) } return store.User{}, err } return user, nil } func (a *Authenticator) createUserFromExternal(ctx context.Context, resp *oidc.IntrospectionResponse) (store.User, error) { user := store.User{ ID: uuid.New(), Username: resp.Username, } err := a.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { _, err := tx.NewInsert(). Model(&user). Exec(ctx) if err != nil { return err } _, err = tx.NewInsert(). Model(&store.ExternalLogin{ UserID: user.ID, Issuer: resp.Issuer, Subject: resp.Subject, }). Exec(ctx) if err != nil { return err } return nil }) if err != nil { return store.User{}, err } return user, nil }