diff --git a/.gitignore b/.gitignore index ceb2a149..f780080b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.out vendor db.db +db_plugin.db shared-local-instance.db debug *__debug_bin diff --git a/Makefile b/Makefile index 1e957e26..2eb424f4 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ build: go build -o ./identifo lint: - golangci-lint run -D deadcode,errcheck,unused,varcheck,govet + golangci-lint run -D errcheck,unused,govet build_admin_panel: rm -rf static/admin_panel diff --git a/cmd/config-boltdb.yaml b/cmd/config-boltdb.yaml index 938d154a..9aeb9b3c 100644 --- a/cmd/config-boltdb.yaml +++ b/cmd/config-boltdb.yaml @@ -11,7 +11,12 @@ storage: type: boltdb boltdb: path: ./db.db - userStorage: *storage_settings + userStorage: + type: plugin + plugin: + cmd: ./plugins/bin/bolt-user-storage + params: { "path": "./db_plugin.db" } + redirectStd: true tokenStorage: *storage_settings tokenBlacklist: *storage_settings verificationCodeStorage: *storage_settings diff --git a/go.mod b/go.mod index e6bceb6d..52c45a6a 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/sessions v1.2.1 + github.com/hashicorp/go-hclog v0.14.1 github.com/hashicorp/go-plugin v1.4.5 github.com/hummerd/httpdump v0.9.1 github.com/joho/godotenv v1.4.0 @@ -70,7 +71,6 @@ require ( github.com/golang/snappy v0.0.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/gorilla/securecookie v1.1.1 // indirect - github.com/hashicorp/go-hclog v0.14.1 // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.13.6 // indirect diff --git a/impersonation/plugin/provider.go b/impersonation/plugin/provider.go index d9633da5..006f9aaa 100644 --- a/impersonation/plugin/provider.go +++ b/impersonation/plugin/provider.go @@ -5,6 +5,7 @@ import ( "os/exec" "time" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" grpcShared "github.com/madappgang/identifo/v2/impersonation/grpc/shared" "github.com/madappgang/identifo/v2/impersonation/plugin/shared" @@ -12,7 +13,6 @@ import ( ) func NewImpersonationProvider(settings model.PluginSettings, timeout time.Duration) (model.ImpersonationProvider, error) { - var err error params := []string{} for k, v := range settings.Params { params = append(params, "-"+k) @@ -24,6 +24,10 @@ func NewImpersonationProvider(settings model.PluginSettings, timeout time.Durati Plugins: shared.PluginMap, Cmd: exec.Command(settings.Cmd, params...), AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + Logger: hclog.New(&hclog.LoggerOptions{ + Level: hclog.Debug, + JSONFormat: true, + }), } if settings.RedirectStd { diff --git a/jwt/service/jwt_token_service.go b/jwt/service/jwt_token_service.go index a86a3964..31efabca 100644 --- a/jwt/service/jwt_token_service.go +++ b/jwt/service/jwt_token_service.go @@ -217,7 +217,7 @@ func (ts *JWTokenService) ValidateTokenString(tstr string, v jwtValidator.Valida // NewAccessToken creates new access token for user. func (ts *JWTokenService) NewAccessToken( user model.User, - scopes []string, + scopes model.AllowedScopesSet, app model.AppData, requireTFA bool, tokenPayload map[string]interface{}, @@ -235,10 +235,11 @@ func (ts *JWTokenService) NewAccessToken( payload[PayloadName] = user.Username } - tokenType := model.TokenTypeAccess + scopesStr := scopes.String() if requireTFA { - scopes = []string{model.TokenTypeTFAPreauth} + scopesStr = model.TokenTypeTFAPreauth } + if len(tokenPayload) > 0 { for k, v := range tokenPayload { payload[k] = v @@ -253,9 +254,9 @@ func (ts *JWTokenService) NewAccessToken( } claims := &model.Claims{ - Scopes: strings.Join(scopes, " "), + Scopes: scopesStr, Payload: payload, - Type: tokenType, + Type: model.TokenTypeAccess, StandardClaims: jwt.StandardClaims{ ExpiresAt: (now + lifespan), Issuer: ts.issuer, @@ -278,22 +279,27 @@ func (ts *JWTokenService) NewAccessToken( } // NewRefreshToken creates new refresh token. -func (ts *JWTokenService) NewRefreshToken(u model.User, scopes []string, app model.AppData) (model.Token, error) { +func (ts *JWTokenService) NewRefreshToken( + user model.User, + scopes model.AllowedScopesSet, + app model.AppData, +) (model.Token, error) { if !app.Active || !app.Offline { return nil, ErrInvalidApp } + // no offline request - if !model.SliceContains(scopes, model.OfflineScope) { + if !scopes.Contains(model.OfflineScope) { return nil, ErrInvalidOfflineScope } - if !u.Active { + if !user.Active { return nil, ErrInvalidUser } payload := make(map[string]interface{}) if model.SliceContains(app.TokenPayload, PayloadName) { - payload[PayloadName] = u.Username + payload[PayloadName] = user.Username } now := ijwt.TimeFunc().Unix() @@ -303,13 +309,13 @@ func (ts *JWTokenService) NewRefreshToken(u model.User, scopes []string, app mod } claims := &model.Claims{ - Scopes: strings.Join(scopes, " "), + Scopes: scopes.String(), Payload: payload, Type: model.TokenTypeRefresh, StandardClaims: jwt.StandardClaims{ ExpiresAt: (now + lifespan), Issuer: ts.issuer, - Subject: u.ID, + Subject: user.ID, Audience: app.ID, IssuedAt: now, }, @@ -338,7 +344,10 @@ func (ts *JWTokenService) NewRefreshToken(u model.User, scopes []string, app mod } // RefreshAccessToken issues new access token for provided refresh token. -func (ts *JWTokenService) RefreshAccessToken(refreshToken model.Token, tokenPayload map[string]interface{}) (model.Token, error) { +func (ts *JWTokenService) RefreshAccessToken( + refreshToken model.Token, + tokenPayload map[string]interface{}, +) (model.Token, error) { rt, ok := refreshToken.(*model.JWToken) if !ok || rt == nil { return nil, model.ErrTokenInvalid diff --git a/jwt/token_test.go b/jwt/token_test.go index 15d2448e..20e8ff3b 100644 --- a/jwt/token_test.go +++ b/jwt/token_test.go @@ -113,6 +113,7 @@ func TestNewToken(t *testing.T) { }, "password", "admin", false) scopes := []string{"scope1", "scope2"} tokenPayload := []string{"name"} + app := model.AppData{ ID: "123456", Secret: "1", @@ -138,7 +139,10 @@ func TestNewToken(t *testing.T) { RolesBlacklist: []string{}, NewUserDefaultRole: "", } - token, err := tokenService.NewAccessToken(user, scopes, app, false, nil) + + allowedScopes := model.AllowedScopes(scopes, scopes, false) + + token, err := tokenService.NewAccessToken(user, allowedScopes, app, false, nil) assert.NoError(t, err) tokenString, err := tokenService.String(token) diff --git a/model/slice.go b/model/slice.go index bc799adc..61065e8d 100644 --- a/model/slice.go +++ b/model/slice.go @@ -18,11 +18,15 @@ func SliceIntersect(a, b []string) []string { } func SliceContains(s []string, e string) bool { + el := strings.TrimSpace(e) + for _, a := range s { - if strings.TrimSpace(strings.ToLower(a)) == strings.TrimSpace(strings.ToLower(e)) { + if strings.EqualFold(strings.TrimSpace(a), el) { return true } + } + return false } diff --git a/model/token.go b/model/token.go index 1b12572d..15892ca4 100644 --- a/model/token.go +++ b/model/token.go @@ -1,6 +1,7 @@ package model import ( + "strings" "time" jwt "github.com/golang-jwt/jwt/v4" @@ -182,15 +183,31 @@ type Claims struct { // Full example of how to use JWT tokens: // https://github.com/form3tech-oss/jwt-go/blob/master/cmd/jwt/app.go -func AllowedScopes(requestedScopes, userScopes []string, isOffline bool) []string { - scopes := []string{} +// This type is needed for guard against passing unchecked scopes to the token. +// Do not convert user provided scopes to this type directly. +type AllowedScopesSet struct { + scopes []string +} + +func (a AllowedScopesSet) String() string { + return strings.Join(a.scopes, " ") +} + +func (a AllowedScopesSet) Scopes() []string { + return a.scopes +} + +func (a AllowedScopesSet) Contains(scope string) bool { + return SliceContains(a.scopes, scope) +} + +func AllowedScopes(requestedScopes, userScopes []string, isOffline bool) AllowedScopesSet { // if we requested any scope, let's provide all the scopes user has and requested - if len(requestedScopes) > 0 { - scopes = SliceIntersect(requestedScopes, userScopes) - } - if SliceContains(requestedScopes, "offline") && isOffline { - scopes = append(scopes, "offline") + scopes := SliceIntersect(requestedScopes, userScopes) + + if SliceContains(requestedScopes, OfflineScope) && isOffline { + scopes = append(scopes, OfflineScope) } - return scopes + return AllowedScopesSet{scopes} } diff --git a/model/token_service.go b/model/token_service.go index 58d555d6..de319424 100644 --- a/model/token_service.go +++ b/model/token_service.go @@ -7,8 +7,8 @@ const ( // TokenService is an abstract token manager. type TokenService interface { - NewAccessToken(u User, scopes []string, app AppData, requireTFA bool, tokenPayload map[string]interface{}) (Token, error) - NewRefreshToken(u User, scopes []string, app AppData) (Token, error) + NewAccessToken(u User, scopes AllowedScopesSet, app AppData, requireTFA bool, tokenPayload map[string]interface{}) (Token, error) + NewRefreshToken(u User, scopes AllowedScopesSet, app AppData) (Token, error) RefreshAccessToken(token Token, tokenPayload map[string]interface{}) (Token, error) NewInviteToken(email, role, audience string, data map[string]interface{}) (Token, error) NewResetToken(userID string) (Token, error) diff --git a/plugins/bolt-user-storage/main.go b/plugins/bolt-user-storage/main.go index eff49faa..f2ac1adb 100644 --- a/plugins/bolt-user-storage/main.go +++ b/plugins/bolt-user-storage/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "log/slog" "os" "os/signal" "syscall" @@ -13,7 +14,20 @@ import ( "github.com/madappgang/identifo/v2/storage/plugin/shared" ) +type wproxy struct { +} + +func (w wproxy) Write(p []byte) (n int, err error) { + return os.Stderr.Write(p) +} + func main() { + slog.SetDefault(slog.New(slog.NewJSONHandler( + wproxy{}, + &slog.HandlerOptions{ + Level: slog.LevelDebug, + }))) + path := flag.String("path", "", "path to database") flag.Parse() @@ -34,7 +48,6 @@ func main() { Plugins: map[string]plugin.Plugin{ "user-storage": &shared.UserStoragePlugin{Impl: s}, }, - // A non-nil value here enables gRPC serving for this plugin... GRPCServer: plugin.DefaultGRPCServer, }) diff --git a/storage/dynamodb/db.go b/storage/dynamodb/db.go index a6eab32c..f456df6d 100644 --- a/storage/dynamodb/db.go +++ b/storage/dynamodb/db.go @@ -51,12 +51,17 @@ func (db *DB) IsTableExists(table string) (bool, error) { } func (db *DB) DeleteTable(table string) error { - svc := dynamodb.New(session.New()) + sess, err := session.NewSession() + if err != nil { + return err + } + + svc := dynamodb.New(sess) input := &dynamodb.DeleteTableInput{ TableName: aws.String(table), } - _, err := svc.DeleteTable(input) + _, err = svc.DeleteTable(input) return err } diff --git a/storage/dynamodb/invite.go b/storage/dynamodb/invite.go index 8e8f1316..d3520a9d 100644 --- a/storage/dynamodb/invite.go +++ b/storage/dynamodb/invite.go @@ -270,6 +270,7 @@ func (is *InviteStorage) ArchiveAllByEmail(email string) error { if err != nil { is.logger.Error("Error querying for invites", logging.FieldError, err) + return ErrorInternalError } diff --git a/storage/dynamodb/user.go b/storage/dynamodb/user.go index e74718e2..25b709cc 100644 --- a/storage/dynamodb/user.go +++ b/storage/dynamodb/user.go @@ -90,6 +90,7 @@ func (us *UserStorage) UserByID(id string) (model.User, error) { if err != nil { us.logger.Error("Error getting item from DynamoDB", logging.FieldError, err) + return model.User{}, ErrorInternalError } if result.Item == nil { diff --git a/storage/plugin/user.go b/storage/plugin/user.go index 33eab9a3..cb8a2549 100644 --- a/storage/plugin/user.go +++ b/storage/plugin/user.go @@ -4,6 +4,7 @@ import ( "os" "os/exec" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "github.com/madappgang/identifo/v2/model" grpcShared "github.com/madappgang/identifo/v2/storage/grpc/shared" @@ -12,7 +13,6 @@ import ( // NewUserStorage creates and inits plugin user storage. func NewUserStorage(settings model.PluginSettings) (model.UserStorage, error) { - var err error params := []string{} for k, v := range settings.Params { params = append(params, "-"+k) @@ -24,6 +24,10 @@ func NewUserStorage(settings model.PluginSettings) (model.UserStorage, error) { Plugins: shared.PluginMap, Cmd: exec.Command(settings.Cmd, params...), AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + Logger: hclog.New(&hclog.LoggerOptions{ + Level: hclog.Debug, + JSONFormat: true, + }), } if settings.RedirectStd { diff --git a/user_payload_provider/plugin/provider.go b/user_payload_provider/plugin/provider.go index 29ac4d59..7591a9d4 100644 --- a/user_payload_provider/plugin/provider.go +++ b/user_payload_provider/plugin/provider.go @@ -5,6 +5,7 @@ import ( "os/exec" "time" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "github.com/madappgang/identifo/v2/model" grpcShared "github.com/madappgang/identifo/v2/user_payload_provider/grpc/shared" @@ -13,7 +14,6 @@ import ( // NewTokenPayloadProvider creates and inits plugin for payload provider. func NewTokenPayloadProvider(settings model.PluginSettings, timeout time.Duration) (model.TokenPayloadProvider, error) { - var err error params := []string{} for k, v := range settings.Params { params = append(params, "-"+k) @@ -21,11 +21,14 @@ func NewTokenPayloadProvider(settings model.PluginSettings, timeout time.Duratio } cfg := &plugin.ClientConfig{ - SyncStdout: os.Stdout, HandshakeConfig: shared.Handshake, Plugins: shared.PluginMap, Cmd: exec.Command(settings.Cmd, params...), AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + Logger: hclog.New(&hclog.LoggerOptions{ + Level: hclog.Debug, + JSONFormat: true, + }), } if settings.RedirectStd { diff --git a/web/api/2fa.go b/web/api/2fa.go index 4fe69fb0..d09f0fae 100644 --- a/web/api/2fa.go +++ b/web/api/2fa.go @@ -88,7 +88,7 @@ func (ar *Router) EnableTFA() http.HandlerFunc { return } - accessToken, _, err := ar.loginUser(user, []string{}, app, false, true, tokenPayload) + accessToken, _, err := ar.loginUser(user, model.AllowedScopesSet{}, app, true, tokenPayload) if err != nil { ar.Error(w, locale, http.StatusInternalServerError, l.ErrorTokenUnableToCreateAccessTokenError, err) return @@ -274,8 +274,7 @@ func (ar *Router) FinalizeTFA() http.HandlerFunc { return } - createRefreshToken := contains(scopes, model.OfflineScope) - accessToken, refreshToken, err := ar.loginUser(user, scopes, app, createRefreshToken, false, tokenPayload) + accessToken, refreshToken, err := ar.loginUser(user, scopes, app, false, tokenPayload) if err != nil { ar.Error(w, locale, http.StatusInternalServerError, l.ErrorTokenUnableToCreateAccessTokenError, err) return @@ -308,7 +307,7 @@ func (ar *Router) FinalizeTFA() http.HandlerFunc { } ar.journal(JournalOperationLoginWith2FA, - user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes) + user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes.Scopes()) ar.server.Storages().User.UpdateLoginMetadata(user.ID) ar.ServeJSON(w, locale, http.StatusOK, result) diff --git a/web/api/federated_login.go b/web/api/federated_login.go index a0a3d43c..f15945e9 100644 --- a/web/api/federated_login.go +++ b/web/api/federated_login.go @@ -213,7 +213,7 @@ func (ar *Router) FederatedLoginComplete() http.HandlerFunc { authResult.Scopes = fsess.Scopes ar.journal(JournalOperationFederatedLogin, - user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes) + user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes()) ar.ServeJSON(w, locale, http.StatusOK, authResult) } diff --git a/web/api/federated_oidc_login.go b/web/api/federated_oidc_login.go index c0a9a427..2800b0e2 100644 --- a/web/api/federated_oidc_login.go +++ b/web/api/federated_oidc_login.go @@ -246,11 +246,11 @@ func (ar *Router) OIDCLoginComplete(useSession bool) http.HandlerFunc { authResult.CallbackUrl = fsess.CallbackUrl } - authResult.Scopes = resultScopes + authResult.Scopes = resultScopes.Scopes() authResult.ProviderData = *providerData ar.journal(JournalOperationOIDCLogin, - user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes) + user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes()) ar.ServeJSON(w, locale, http.StatusOK, authResult) } diff --git a/web/api/hello.go b/web/api/hello.go index 7181afac..cac77800 100644 --- a/web/api/hello.go +++ b/web/api/hello.go @@ -34,8 +34,6 @@ func (ar *Router) HandlePing(w http.ResponseWriter, r *http.Request) { locale := r.Header.Get("Accept-Language") - ar.logger.Debug("trace pong handler") - pong := pongResponse{ Message: "Pong!", Date: time.Now(), diff --git a/web/api/impersonate_as.go b/web/api/impersonate_as.go index 886ced47..c23d0aef 100644 --- a/web/api/impersonate_as.go +++ b/web/api/impersonate_as.go @@ -78,7 +78,7 @@ func (ar *Router) ImpersonateAs() http.HandlerFunc { authResult.RefreshToken = "" ar.journal(JournalOperationImpersonatedAs, - userID, app.ID, r.UserAgent(), user.AccessRole, resultScopes) + userID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes()) ar.ServeJSON(w, locale, http.StatusOK, authResult) } diff --git a/web/api/journal.go b/web/api/journal.go index c44b3c75..9bc2551e 100644 --- a/web/api/journal.go +++ b/web/api/journal.go @@ -31,8 +31,9 @@ func (ar *Router) journal( "operation", string(op), logging.FieldUserID, userID, logging.FieldAppID, appID, + "device", device, "issuer", iss, + "accessRole", accessRole, "scopes", scopes, - "role", accessRole, - "device", device) + ) } diff --git a/web/api/login.go b/web/api/login.go index a6a238b4..30525d93 100644 --- a/web/api/login.go +++ b/web/api/login.go @@ -190,7 +190,7 @@ func (ar *Router) LoginWithPassword() http.HandlerFunc { } ar.journal(JournalOperationLoginWithPassword, - user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes) + user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes()) ar.ServeJSON(w, locale, http.StatusOK, authResult) } @@ -311,9 +311,9 @@ func (ar *Router) getTokenPayloadService(app model.AppData) (model.TokenPayloadP // createRefreshToken boolean param tells if we should issue refresh token as well. func (ar *Router) loginUser( user model.User, - scopes []string, + scopes model.AllowedScopesSet, app model.AppData, - createRefreshToken, require2FA bool, + require2FA bool, tokenPayload map[string]interface{}, ) (string, string, error) { token, err := ar.server.Services().Token.NewAccessToken(user, scopes, app, require2FA, tokenPayload) @@ -325,6 +325,9 @@ func (ar *Router) loginUser( if err != nil { return "", "", err } + + createRefreshToken := scopes.Contains(model.OfflineScope) + if !createRefreshToken || require2FA { return accessTokenString, "", nil } @@ -335,10 +338,12 @@ func (ar *Router) loginUser( logging.FieldError, err) return accessTokenString, "", nil } + refreshTokenString, err := ar.server.Services().Token.String(refresh) if err != nil { return "", "", err } + return accessTokenString, refreshTokenString, nil } @@ -347,11 +352,11 @@ func (ar *Router) loginFlow( user model.User, requestedScopes []string, additionalPayload map[string]any, -) (AuthResponse, []string, error) { +) (AuthResponse, model.AllowedScopesSet, error) { // check if the user has the scope, that allows to login to the app // user has to have at least one scope app expecting if len(app.Scopes) > 0 && len(model.SliceIntersect(app.Scopes, user.Scopes)) == 0 { - return AuthResponse{}, nil, errors.New("user does not have required scope for the app") + return AuthResponse{}, model.AllowedScopesSet{}, errors.New("user does not have required scope for the app") } // Do login flow. @@ -360,13 +365,12 @@ func (ar *Router) loginFlow( // Check if we should require user to authenticate with 2FA. require2FA, enabled2FA, err := ar.check2FA(app.TFAStatus, ar.tfaType, user) if !require2FA && enabled2FA && err != nil { - return AuthResponse{}, nil, err + return AuthResponse{}, model.AllowedScopesSet{}, err } - offline := contains(scopes, model.OfflineScope) tokenPayload, err := ar.getTokenPayloadForApp(app, user.ID) if err != nil { - return AuthResponse{}, nil, err + return AuthResponse{}, model.AllowedScopesSet{}, err } if tokenPayload == nil { @@ -377,9 +381,9 @@ func (ar *Router) loginFlow( } } - accessToken, refreshToken, err := ar.loginUser(user, scopes, app, offline, require2FA, tokenPayload) + accessToken, refreshToken, err := ar.loginUser(user, scopes, app, require2FA, tokenPayload) if err != nil { - return AuthResponse{}, nil, err + return AuthResponse{}, model.AllowedScopesSet{}, err } result := AuthResponse{ @@ -391,7 +395,7 @@ func (ar *Router) loginFlow( if require2FA && enabled2FA { if err := ar.sendOTPCode(app, user); err != nil { - return AuthResponse{}, nil, err + return AuthResponse{}, model.AllowedScopesSet{}, err } } else { ar.server.Storages().User.UpdateLoginMetadata(user.ID) @@ -461,12 +465,14 @@ func (ar *Router) GetImpersonateToken() http.HandlerFunc { } // getImpersonateAccessToken creates and returns access token for a user. -func (ar *Router) getImpersonateAccessToken(user model.User, scopes []string, app model.AppData) (string, error) { +func (ar *Router) getImpersonateAccessToken(user model.User, requestedScopes []string, app model.AppData) (string, error) { tokenPayload, err := ar.getTokenPayloadForApp(app, user.ID) if err != nil { return "", err } + scopes := model.AllowedScopes(requestedScopes, user.Scopes, app.Offline) + token, err := ar.server.Services().Token.NewAccessToken(user, scopes, app, false, tokenPayload) if err != nil { return "", err diff --git a/web/api/phone_login.go b/web/api/phone_login.go index 9b48dcc3..9a17cc67 100644 --- a/web/api/phone_login.go +++ b/web/api/phone_login.go @@ -156,8 +156,7 @@ func (ar *Router) PhoneLogin() http.HandlerFunc { return } - offline := contains(scopes, model.OfflineScope) - accessToken, refreshToken, err := ar.loginUser(user, scopes, app, offline, false, tokenPayload) + accessToken, refreshToken, err := ar.loginUser(user, scopes, app, false, tokenPayload) if err != nil { ar.Error(w, locale, http.StatusInternalServerError, l.ErrorAPILoginError, err) return @@ -171,7 +170,7 @@ func (ar *Router) PhoneLogin() http.HandlerFunc { } ar.journal(JournalOperationLoginWithPhone, - user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes) + user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes.Scopes()) ar.server.Storages().User.UpdateLoginMetadata(user.ID) diff --git a/web/api/refresh_token.go b/web/api/refresh_token.go index 857524e4..091b4477 100644 --- a/web/api/refresh_token.go +++ b/web/api/refresh_token.go @@ -101,7 +101,7 @@ func (ar *Router) issueNewRefreshToken( requestedScopes []string, app model.AppData, ) (string, error) { - if !contains(requestedScopes, model.OfflineScope) { // Don't issue new refresh token if not requested. + if !model.SliceContains(requestedScopes, model.OfflineScope) { // Don't issue new refresh token if not requested. return "", nil } diff --git a/web/api/refresh_token_test.go b/web/api/refresh_token_test.go index eb0025b2..d7f40c6c 100644 --- a/web/api/refresh_token_test.go +++ b/web/api/refresh_token_test.go @@ -32,10 +32,13 @@ func TestRefreshTokens(t *testing.T) { tokenService := testServer.Services().Token - refreshToken, err := tokenService.NewRefreshToken( - user, + scopes := model.AllowedScopes( []string{"offline", "chat", "super_admin"}, - testApp) + []string{"offline", "chat", "super_admin"}, + true, + ) + + refreshToken, err := tokenService.NewRefreshToken(user, scopes, testApp) require.NoError(t, err) rts, err := tokenService.String(refreshToken) diff --git a/web/api/registration.go b/web/api/registration.go index 88173c37..006ce8fd 100644 --- a/web/api/registration.go +++ b/web/api/registration.go @@ -180,7 +180,7 @@ func (ar *Router) RegisterWithPassword() http.HandlerFunc { } ar.journal(JournalOperationRegistration, - user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes) + user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes()) ar.ServeJSON(w, locale, http.StatusOK, authResult) } diff --git a/web/api/routes.go b/web/api/routes.go index bbb36b11..3258cf4c 100644 --- a/web/api/routes.go +++ b/web/api/routes.go @@ -19,6 +19,12 @@ func (ar *Router) initRoutes( panic("Empty API router") } + pingHandler := negroni.New( + negroni.NewRecovery(), + negroni.WrapFunc(ar.HandlePing), + ) + ar.router.Handle("/ping", pingHandler).Methods(http.MethodGet) + baseMiddleware := buildBaseMiddleware( loggerSettings.DumpRequest, loggerSettings.Format, @@ -26,10 +32,6 @@ func (ar *Router) initRoutes( loggerSettings.LogSensitiveData, ar.cors, ) - - ph := with(baseMiddleware, negroni.WrapFunc(ar.HandlePing)) - ar.router.Handle("/ping", ph).Methods(http.MethodGet) - apiMiddlewares := ar.buildAPIMiddleware(baseMiddleware) // federated oidc diff --git a/web/api/utils.go b/web/api/utils.go deleted file mode 100644 index 655ab75c..00000000 --- a/web/api/utils.go +++ /dev/null @@ -1,12 +0,0 @@ -package api - -import "strings" - -func contains(s []string, e string) bool { - for _, a := range s { - if strings.EqualFold(strings.TrimSpace(a), strings.TrimSpace(e)) { - return true - } - } - return false -}