diff --git a/backend/internal/common/errors.go b/backend/internal/common/errors.go index 873054de..14536541 100644 --- a/backend/internal/common/errors.go +++ b/backend/internal/common/errors.go @@ -88,11 +88,6 @@ type NotSignedInError struct{} func (e NotSignedInError) Error() string { return "You are not signed in" } func (e NotSignedInError) HttpStatusCode() int { return http.StatusUnauthorized } -type MissingAccessToken struct{} - -func (e MissingAccessToken) Error() string { return "Missing access token" } -func (e MissingAccessToken) HttpStatusCode() int { return http.StatusUnauthorized } - type MissingPermissionError struct{} func (e MissingPermissionError) Error() string { @@ -115,11 +110,6 @@ type UserNotFoundError struct{} func (e UserNotFoundError) Error() string { return "User not found" } func (e UserNotFoundError) HttpStatusCode() int { return http.StatusNotFound } -type ClientIdOrSecretNotProvidedError struct{} - -func (e ClientIdOrSecretNotProvidedError) Error() string { return "Client id or secret not provided" } -func (e ClientIdOrSecretNotProvidedError) HttpStatusCode() int { return http.StatusBadRequest } - type WrongFileTypeError struct { ExpectedFileType string } diff --git a/backend/internal/dto/dto_normalize.go b/backend/internal/dto/dto_normalize.go index b73910c4..b988e84e 100644 --- a/backend/internal/dto/dto_normalize.go +++ b/backend/internal/dto/dto_normalize.go @@ -1,7 +1,6 @@ package dto import ( - "net/http" "reflect" "github.com/gin-gonic/gin" @@ -73,22 +72,3 @@ loop: func ShouldBindWithNormalizedJSON(ctx *gin.Context, obj any) error { return ctx.ShouldBindWith(obj, binding.JSON) } - -type NormalizerJSONBinding struct{} - -func (NormalizerJSONBinding) Name() string { - return "json" -} - -func (NormalizerJSONBinding) Bind(req *http.Request, obj any) error { - // Use the default JSON binder - err := binding.JSON.Bind(req, obj) - if err != nil { - return err - } - - // Perform normalization - Normalize(obj) - - return nil -} diff --git a/backend/internal/oidc/authorization_service_test.go b/backend/internal/oidc/authorization_service_test.go index 15659015..6add3972 100644 --- a/backend/internal/oidc/authorization_service_test.go +++ b/backend/internal/oidc/authorization_service_test.go @@ -304,13 +304,12 @@ func TestAuthorizationServiceAuthorizeSwitchesUserAndResetsRequirements(t *testi ClientID: clientID, Scope: datatype.StringList{"openid"}, }).Error) - reauthenticatedAt := datatype.DateTime(time.Now().Add(-time.Minute).UTC()) require.NoError(t, db.Create(&InteractionSession{ Base: model.Base{ID: interactionID}, Scopes: datatype.StringList{"openid"}, ClientID: clientID, UserID: stringPointer(userID), - ReauthenticatedAt: &reauthenticatedAt, + ReauthenticatedAt: new(datatype.DateTime(time.Now().Add(-time.Minute).UTC())), ReauthenticationRequired: false, RequestedAt: datatype.DateTime(time.Now().UTC()), Parameters: map[string]string{ @@ -766,13 +765,12 @@ func TestAuthorizationServiceAuthorizeUsesCompletedReauthenticationTime(t *testi ClientID: clientID, Scope: datatype.StringList{"openid"}, }).Error) - reauthenticatedAtValue := datatype.DateTime(reauthenticatedAt) require.NoError(t, db.Create(&InteractionSession{ Base: model.Base{ID: interactionID}, Scopes: datatype.StringList{"openid"}, ClientID: clientID, ReauthenticationRequired: false, - ReauthenticatedAt: &reauthenticatedAtValue, + ReauthenticatedAt: new(datatype.DateTime(reauthenticatedAt)), Parameters: map[string]string{ "max_age": "1", }, @@ -815,14 +813,13 @@ func TestAuthorizationServiceAuthorizeUsesOriginalInteractionRequestTime(t *test ClientID: clientID, Scope: datatype.StringList{"openid"}, }).Error) - reauthenticatedAtValue := datatype.DateTime(reauthenticatedAt) require.NoError(t, db.Create(&InteractionSession{ Base: model.Base{ID: interactionID}, Scopes: datatype.StringList{"openid"}, ClientID: clientID, ReauthenticationRequired: false, RequestedAt: datatype.DateTime(originalRequestedAt), - ReauthenticatedAt: &reauthenticatedAtValue, + ReauthenticatedAt: new(datatype.DateTime(reauthenticatedAt)), Parameters: map[string]string{ "prompt": "login", }, diff --git a/backend/internal/oidc/cleanup_test.go b/backend/internal/oidc/cleanup_test.go index 1e367907..df1ff251 100644 --- a/backend/internal/oidc/cleanup_test.go +++ b/backend/internal/oidc/cleanup_test.go @@ -18,11 +18,10 @@ import ( func TestCleanupExpiredOAuth2SessionsKeepsInvalidatedButUnexpiredSessions(t *testing.T) { db := testutils.NewDatabaseForTest(t) - past := datatype.DateTime(time.Now().Add(-time.Hour)) future := datatype.DateTime(time.Now().Add(time.Hour)) rows := []OAuth2Session{ - {Base: model.Base{ID: "expired"}, Kind: "access_token", Key: "k-expired", RequestID: "r1", Active: true, RequestData: "{}", ExpiresAt: &past}, + {Base: model.Base{ID: "expired"}, Kind: "access_token", Key: "k-expired", RequestID: "r1", Active: true, RequestData: "{}", ExpiresAt: new(datatype.DateTime(time.Now().Add(-time.Hour)))}, {Base: model.Base{ID: "rotated"}, Kind: "refresh_token", Key: "k-rotated", RequestID: "r2", Active: false, RequestData: "{}", ExpiresAt: &future}, {Base: model.Base{ID: "active"}, Kind: "refresh_token", Key: "k-active", RequestID: "r3", Active: true, RequestData: "{}", ExpiresAt: &future}, } diff --git a/backend/internal/service/jwt_service.go b/backend/internal/service/jwt_service.go index 5eafd310..41ac6937 100644 --- a/backend/internal/service/jwt_service.go +++ b/backend/internal/service/jwt_service.go @@ -290,19 +290,6 @@ func (s *JwtService) GetKeyID() (string, bool) { return s.privateKey.KeyID() } -// GetIsAdmin returns the value of the "isAdmin" claim in the token -func GetIsAdmin(token jwt.Token) (bool, error) { - if !token.Has(IsAdminClaim) { - return false, nil - } - var isAdmin bool - err := token.Get(IsAdminClaim, &isAdmin) - if err != nil { - return false, fmt.Errorf("failed to get 'isAdmin' claim from token: %w", err) - } - return isAdmin, nil -} - // GetAuthenticationMethod returns the first authentication method in the "amr" claim in the token func GetAuthenticationMethod(token jwt.Token) (string, error) { if !token.Has(common.AuthenticationMethodsClaim) { diff --git a/backend/internal/service/jwt_service_test.go b/backend/internal/service/jwt_service_test.go index 75408bfc..8d3f94aa 100644 --- a/backend/internal/service/jwt_service_test.go +++ b/backend/internal/service/jwt_service_test.go @@ -319,9 +319,11 @@ func TestGenerateVerifyAccessToken(t *testing.T) { subject, ok := claims.Subject() _ = assert.True(t, ok, "User ID not found in token") && assert.Equal(t, user.ID, subject, "Token subject should match user ID") - isAdmin, err := GetIsAdmin(claims) - _ = assert.NoError(t, err, "Failed to get isAdmin claim") && - assert.False(t, isAdmin, "isAdmin should be false") + isAdmin := false + if claims.Has(IsAdminClaim) { + require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + } + assert.False(t, isAdmin, "isAdmin should be false") authenticationMethod, err := GetAuthenticationMethod(claims) _ = assert.NoError(t, err, "Failed to get amr claim") && assert.Empty(t, authenticationMethod, "amr should be empty when not specified") @@ -354,9 +356,11 @@ func TestGenerateVerifyAccessToken(t *testing.T) { claims, err := service.VerifyAccessToken(tokenString) require.NoError(t, err, "Failed to verify generated token") - isAdmin, err := GetIsAdmin(claims) - _ = assert.NoError(t, err, "Failed to get isAdmin claim") && - assert.True(t, isAdmin, "isAdmin should be true") + isAdmin := false + if claims.Has(IsAdminClaim) { + require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + } + assert.True(t, isAdmin, "isAdmin should be true") subject, ok := claims.Subject() _ = assert.True(t, ok, "User ID not found in token") && assert.Equal(t, adminUser.ID, subject, "Token subject should match user ID") @@ -428,9 +432,11 @@ func TestGenerateVerifyAccessToken(t *testing.T) { subject, ok := claims.Subject() _ = assert.True(t, ok, "User ID not found in token") && assert.Equal(t, user.ID, subject, "Token subject should match user ID") - isAdmin, err := GetIsAdmin(claims) - _ = assert.NoError(t, err, "Failed to get isAdmin claim") && - assert.True(t, isAdmin, "isAdmin should be true") + isAdmin := false + if claims.Has(IsAdminClaim) { + require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + } + assert.True(t, isAdmin, "isAdmin should be true") publicKey, err := service.GetPublicJWK() require.NoError(t, err) @@ -465,9 +471,11 @@ func TestGenerateVerifyAccessToken(t *testing.T) { subject, ok := claims.Subject() _ = assert.True(t, ok, "User ID not found in token") && assert.Equal(t, user.ID, subject, "Token subject should match user ID") - isAdmin, err := GetIsAdmin(claims) - _ = assert.NoError(t, err, "Failed to get isAdmin claim") && - assert.True(t, isAdmin, "isAdmin should be true") + isAdmin := false + if claims.Has(IsAdminClaim) { + require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + } + assert.True(t, isAdmin, "isAdmin should be true") publicKey, err := service.GetPublicJWK() require.NoError(t, err) @@ -502,9 +510,11 @@ func TestGenerateVerifyAccessToken(t *testing.T) { subject, ok := claims.Subject() _ = assert.True(t, ok, "User ID not found in token") && assert.Equal(t, user.ID, subject, "Token subject should match user ID") - isAdmin, err := GetIsAdmin(claims) - _ = assert.NoError(t, err, "Failed to get isAdmin claim") && - assert.True(t, isAdmin, "isAdmin should be true") + isAdmin := false + if claims.Has(IsAdminClaim) { + require.NoError(t, claims.Get(IsAdminClaim, &isAdmin), "Failed to get isAdmin claim") + } + assert.True(t, isAdmin, "isAdmin should be true") publicKey, err := service.GetPublicJWK() require.NoError(t, err) diff --git a/backend/internal/utils/file_util.go b/backend/internal/utils/file_util.go index 15863068..6a31d993 100644 --- a/backend/internal/utils/file_util.go +++ b/backend/internal/utils/file_util.go @@ -81,18 +81,6 @@ func GetImageExtensionFromMimeType(mimeType string) string { } } -// FileExists returns true if a file exists on disk and is a regular file -func FileExists(path string) (bool, error) { - s, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - return !s.IsDir(), nil -} - // IsWritableDir checks if a directory exists and is writable func IsWritableDir(dir string) (bool, error) { // Check if directory exists and it's actually a directory diff --git a/backend/internal/utils/hash_util.go b/backend/internal/utils/hash_util.go index 0fc37d27..d80c41f1 100644 --- a/backend/internal/utils/hash_util.go +++ b/backend/internal/utils/hash_util.go @@ -3,28 +3,9 @@ package utils import ( "crypto/sha256" "encoding/hex" - "fmt" - "io" - "os" ) func CreateSha256Hash(input string) string { hash := sha256.Sum256([]byte(input)) return hex.EncodeToString(hash[:]) } - -func CreateSha256FileHash(filePath string) ([]byte, error) { - f, err := os.Open(filePath) - if err != nil { - return nil, fmt.Errorf("failed to open file: %w", err) - } - defer f.Close() - - h := sha256.New() - _, err = io.Copy(h, f) - if err != nil { - return nil, fmt.Errorf("failed to read file: %w", err) - } - - return h.Sum(nil), nil -} diff --git a/backend/internal/utils/http_util.go b/backend/internal/utils/http_util.go index bd9cc028..06811c4b 100644 --- a/backend/internal/utils/http_util.go +++ b/backend/internal/utils/http_util.go @@ -1,48 +1,12 @@ package utils import ( - "net/http" - "net/url" "strconv" - "strings" "time" "github.com/gin-gonic/gin" ) -// BearerAuth returns the value of the bearer token in the Authorization header if present -func BearerAuth(r *http.Request) (string, bool) { - const prefix = "bearer " - - authHeader := r.Header.Get("Authorization") - if len(authHeader) >= len(prefix) && strings.ToLower(authHeader[:len(prefix)]) == prefix { - return authHeader[len(prefix):], true - } - - return "", false -} - -// OAuthClientBasicAuth returns the OAuth client ID and secret provided in the request's -// Authorization header, if present. See RFC 6749, Section 2.3. -func OAuthClientBasicAuth(r *http.Request) (clientID, clientSecret string, ok bool) { - clientID, clientSecret, ok = r.BasicAuth() - if !ok { - return "", "", false - } - - clientID, err := url.QueryUnescape(clientID) - if err != nil { - return "", "", false - } - - clientSecret, err = url.QueryUnescape(clientSecret) - if err != nil { - return "", "", false - } - - return clientID, clientSecret, true -} - // SetCacheControlHeader sets the Cache-Control header for the response. func SetCacheControlHeader(ctx *gin.Context, maxAge, staleWhileRevalidate time.Duration) { _, ok := ctx.GetQuery("skipCache") diff --git a/backend/internal/utils/http_util_test.go b/backend/internal/utils/http_util_test.go deleted file mode 100644 index e0c828b2..00000000 --- a/backend/internal/utils/http_util_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package utils - -import ( - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestBearerAuth(t *testing.T) { - tests := []struct { - name string - authHeader string - expectedToken string - expectedFound bool - }{ - { - name: "Valid bearer token", - authHeader: "Bearer token123", - expectedToken: "token123", - expectedFound: true, - }, - { - name: "Valid bearer token with mixed case", - authHeader: "beARer token456", - expectedToken: "token456", - expectedFound: true, - }, - { - name: "No bearer prefix", - authHeader: "Basic dXNlcjpwYXNz", - expectedToken: "", - expectedFound: false, - }, - { - name: "Empty auth header", - authHeader: "", - expectedToken: "", - expectedFound: false, - }, - { - name: "Bearer prefix only", - authHeader: "Bearer ", - expectedToken: "", - expectedFound: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://example.com", nil) - require.NoError(t, err, "Failed to create request") - - if tt.authHeader != "" { - req.Header.Set("Authorization", tt.authHeader) - } - - token, found := BearerAuth(req) - - assert.Equal(t, tt.expectedFound, found) - assert.Equal(t, tt.expectedToken, token) - }) - } -} - -// #nosec G101 - Test credentials -func TestOAuthClientBasicAuth(t *testing.T) { - tests := []struct { - name string - authHeader string - expectedClientID string - expectedClientSecret string - expectedOk bool - }{ - { - name: "Valid client ID and secret in header (example from RFC 6749)", - authHeader: "Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3", - expectedClientID: "s6BhdRkqt3", - expectedClientSecret: "7Fjfp0ZBr1KtDRbnfVdmIw", - expectedOk: true, - }, - { - name: "Valid client ID and secret in header (escaped values)", - authHeader: "Basic ZTUwOTcyYmQtNmUzMi00OTU3LWJhZmMtMzU0MTU3ZjI1NDViOislMjUlMjYlMkIlQzIlQTMlRTIlODIlQUM=", - expectedClientID: "e50972bd-6e32-4957-bafc-354157f2545b", - // This is the example string from RFC 6749, Appendix B. - expectedClientSecret: " %&+£€", - expectedOk: true, - }, - { - name: "Empty auth header", - authHeader: "", - expectedClientID: "", - expectedClientSecret: "", - expectedOk: false, - }, - { - name: "Basic prefix only", - authHeader: "Basic ", - expectedClientID: "", - expectedClientSecret: "", - expectedOk: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "http://example.com", nil) - require.NoError(t, err, "Failed to create request") - - if tt.authHeader != "" { - req.Header.Set("Authorization", tt.authHeader) - } - - clientId, clientSecret, ok := OAuthClientBasicAuth(req) - - assert.Equal(t, tt.expectedOk, ok) - - if tt.expectedOk { - assert.Equal(t, tt.expectedClientID, clientId) - assert.Equal(t, tt.expectedClientSecret, clientSecret) - } - }) - } -} diff --git a/frontend/messages/en.json b/frontend/messages/en.json index ea6ed82b..4d84fa0a 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -37,8 +37,6 @@ "expiration": "Expiration", "generate_code": "Generate Code", "name": "Name", - "browser_unsupported": "Browser unsupported", - "this_browser_does_not_support_passkeys": "This browser doesn't support passkeys. Please use an alternative sign in method.", "an_unknown_error_occurred": "An unknown error occurred", "authentication_process_was_aborted": "The authentication process was aborted", "error_occurred_with_authenticator": "An error occurred with the authenticator", diff --git a/frontend/package.json b/frontend/package.json index 7fd3a182..7b4a8787 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,7 +19,6 @@ "axios": "^1.16.1", "clsx": "^2.1.1", "date-fns": "^4.2.1", - "jose": "^6.2.3", "qrcode": "^1.5.4", "runed": "^0.37.1", "sveltekit-superforms": "^2.30.1", @@ -35,7 +34,6 @@ "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.60.1", "@sveltejs/vite-plugin-svelte": "^7.1.2", - "@types/eslint": "^9.6.1", "@types/node": "^25.9.0", "@types/qrcode": "^1.5.6", "bits-ui": "^2.18.1", @@ -48,7 +46,6 @@ "prettier": "^3.8.3", "prettier-plugin-svelte": "^3.5.2", "prettier-plugin-tailwindcss": "^0.8.0", - "rollup": "^4.60.4", "shadcn-svelte": "^1.3.0", "svelte": "^5.55.8", "svelte-check": "^4.4.8", diff --git a/frontend/src/lib/components/form/checkbox-with-label.svelte b/frontend/src/lib/components/form/checkbox-with-label.svelte deleted file mode 100644 index e719d3cc..00000000 --- a/frontend/src/lib/components/form/checkbox-with-label.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -
{m.browser_unsupported()}
-- {m.this_browser_does_not_support_passkeys()} -
-