konfa-server/src/auth/auth.go
2024-12-25 17:20:49 +03:00

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
}