156 lines
4.6 KiB
Go
156 lines
4.6 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
authHandlers "github.com/cortezaproject/corteza-server/auth/handlers"
|
|
"github.com/cortezaproject/corteza-server/auth/oauth2"
|
|
"github.com/cortezaproject/corteza-server/pkg/auth"
|
|
"github.com/cortezaproject/corteza-server/pkg/id"
|
|
"github.com/cortezaproject/corteza-server/pkg/options"
|
|
"github.com/cortezaproject/corteza-server/store"
|
|
"github.com/cortezaproject/corteza-server/system/types"
|
|
"github.com/lestrrat-go/jwx/jwa"
|
|
"github.com/lestrrat-go/jwx/jwk"
|
|
"github.com/lestrrat-go/jwx/jwt"
|
|
"go.uber.org/zap"
|
|
"io/ioutil"
|
|
"strings"
|
|
)
|
|
|
|
func (app *CortezaApp) initAuth(ctx context.Context) (err error) {
|
|
log := app.Log.Named("auth")
|
|
|
|
if app.Opt.Auth.DefaultClient != "" {
|
|
// default client will help streamline authorization with default clients
|
|
app.DefaultAuthClient, err = store.LookupAuthClientByHandle(ctx, app.Store, app.Opt.Auth.DefaultClient)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot load default client: %w", err)
|
|
}
|
|
}
|
|
|
|
app.oa2m = oauth2.NewManager(
|
|
app.Opt.Auth,
|
|
app.Log,
|
|
oauth2.NewClientStore(app.Store, app.DefaultAuthClient),
|
|
oauth2.NewTokenStore(app.Store),
|
|
)
|
|
|
|
// set base path for links&routes in auth server
|
|
authHandlers.BasePath = app.Opt.HTTPServer.BaseUrl
|
|
|
|
auth.DefaultSigner = auth.HmacSigner(app.Opt.Auth.Secret)
|
|
|
|
if auth.HttpTokenVerifier, err = auth.TokenVerifierMiddlewareWithSecretSigner(app.Opt.Auth.Secret); err != nil {
|
|
return fmt.Errorf("could not set token verifier")
|
|
}
|
|
|
|
// create token signature function from AUTH_ options
|
|
// and pass it on to the token issuer
|
|
alg, key, err := prepareSignatureFnParams(app.Opt.Auth)
|
|
if err != nil {
|
|
return fmt.Errorf("could not initialize token signer: %w", err)
|
|
}
|
|
|
|
log.Info(
|
|
"initializing JWT and authentication procedures",
|
|
zap.Stringer("algoritm", alg),
|
|
)
|
|
|
|
// construct token issuer with algorithm, secrets,
|
|
auth.TokenIssuer, err = auth.NewTokenIssuer(
|
|
auth.WithSigner(func(t jwt.Token) ([]byte, error) {
|
|
return jwt.Sign(t, alg, key)
|
|
}),
|
|
|
|
// @todo implement configurable issuer claim
|
|
//auth.WithDefaultIssuer(app.Opt.Auth.TokenClaimIssuer),
|
|
auth.WithDefaultExpiration(app.Opt.Auth.AccessTokenLifetime),
|
|
auth.WithDefaultClientID(app.DefaultAuthClient.ID),
|
|
auth.WithLookup(func(ctx context.Context, accessToken string) (err error) {
|
|
_, err = store.LookupAuthOa2tokenByAccess(ctx, app.Store, accessToken)
|
|
return err
|
|
}),
|
|
auth.WithStore(func(ctx context.Context, req auth.TokenRequest) error {
|
|
var (
|
|
eti = auth.GetExtraReqInfoFromContext(ctx)
|
|
createdAt = req.IssuedAt
|
|
|
|
oa2t = &types.AuthOa2token{
|
|
ID: id.Next(),
|
|
Access: req.AccessToken,
|
|
Refresh: req.RefreshToken,
|
|
CreatedAt: createdAt,
|
|
RemoteAddr: eti.RemoteAddr,
|
|
UserAgent: eti.UserAgent,
|
|
ClientID: req.ClientID,
|
|
UserID: req.UserID,
|
|
ExpiresAt: createdAt.Add(req.Expiration),
|
|
}
|
|
)
|
|
|
|
return store.CreateAuthOa2token(ctx, app.Store, oa2t)
|
|
}),
|
|
)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not initialize token issuer: %w", err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// helper function that loads and/or parses private keys and initializes JWT signer function
|
|
// from the given arguments
|
|
func prepareSignatureFnParams(opt options.AuthOpt) (alg jwa.SignatureAlgorithm, key any, err error) {
|
|
alg = jwa.SignatureAlgorithm(opt.JwtAlgorithm)
|
|
|
|
switch alg {
|
|
case jwa.HS256, jwa.HS384, jwa.HS512:
|
|
// expecting secret to be set
|
|
if len(opt.Secret) == 0 {
|
|
return alg, nil, fmt.Errorf("token secret missing")
|
|
}
|
|
|
|
key = []byte(opt.Secret)
|
|
case
|
|
jwa.PS256, jwa.PS384, jwa.PS512,
|
|
jwa.RS256, jwa.RS384, jwa.RS512:
|
|
if len(opt.JwtKey) == 0 {
|
|
return alg, nil, fmt.Errorf("token key missing")
|
|
}
|
|
|
|
// if given key dos not begins with "-----BEGIN"
|
|
// assume it's path to a file and load contents of that file
|
|
if !strings.HasPrefix(opt.JwtKey, "-----BEGIN") {
|
|
var (
|
|
keyFile = opt.JwtKey
|
|
b []byte
|
|
)
|
|
if b, err = ioutil.ReadFile(keyFile); err != nil {
|
|
return alg, nil, fmt.Errorf("could not read key file: %w", err)
|
|
}
|
|
|
|
// overwrite th input and load
|
|
opt.JwtKey = string(b)
|
|
|
|
// recheck contents of the key
|
|
if !strings.HasPrefix(opt.JwtKey, "-----BEGIN") {
|
|
return alg, nil, fmt.Errorf("file %q does not contain a valid private key", keyFile)
|
|
}
|
|
}
|
|
|
|
// generates pem.Private from the kInput string
|
|
key, err = jwk.ParseKey([]byte(opt.JwtKey), jwk.WithPEM(true))
|
|
case "":
|
|
// should be caught by options init procedure and set to default,
|
|
// but you never know...
|
|
err = fmt.Errorf("token signature algorithm empty or missing")
|
|
|
|
default:
|
|
err = fmt.Errorf("token signature algorithm %q not supported", alg)
|
|
}
|
|
|
|
return
|
|
}
|