120 lines
2.6 KiB
Go
120 lines
2.6 KiB
Go
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
|
|
}
|