mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-10 15:22:56 +03:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80f108e5d6 | ||
|
|
9b2d622990 | ||
|
|
adf74586af | ||
|
|
b45cf68295 | ||
|
|
d9dd67c51f | ||
|
|
abf17f6211 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,3 +1,16 @@
|
||||
## [](https://github.com/pocket-id/pocket-id/compare/v0.46.0...v) (2025-04-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add qrcode representation of one time link ([#424](https://github.com/pocket-id/pocket-id/issues/424)) ([#436](https://github.com/pocket-id/pocket-id/issues/436)) ([abf17f6](https://github.com/pocket-id/pocket-id/commit/abf17f62114a2de549b62cec462b9b0659ee23a7))
|
||||
* disable animations setting toggle ([#442](https://github.com/pocket-id/pocket-id/issues/442)) ([b45cf68](https://github.com/pocket-id/pocket-id/commit/b45cf68295975f51777dab95950b98b8db0a9ae5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* define token type as claim for better client compatibility ([adf7458](https://github.com/pocket-id/pocket-id/commit/adf74586afb6ef9a00fb122c150b0248c5bc23f0))
|
||||
|
||||
## [](https://github.com/pocket-id/pocket-id/compare/v0.45.0...v) (2025-04-13)
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ type AppConfigUpdateDto struct {
|
||||
AppName string `json:"appName" binding:"required,min=1,max=30"`
|
||||
SessionDuration string `json:"sessionDuration" binding:"required"`
|
||||
EmailsVerified string `json:"emailsVerified" binding:"required"`
|
||||
DisableAnimations string `json:"disableAnimations" binding:"required"`
|
||||
AllowOwnAccountEdit string `json:"allowOwnAccountEdit" binding:"required"`
|
||||
SmtpHost string `json:"smtpHost"`
|
||||
SmtpPort string `json:"smtpPort"`
|
||||
|
||||
@@ -34,6 +34,7 @@ type AppConfig struct {
|
||||
AppName AppConfigVariable `key:"appName,public"` // Public
|
||||
SessionDuration AppConfigVariable `key:"sessionDuration"`
|
||||
EmailsVerified AppConfigVariable `key:"emailsVerified"`
|
||||
DisableAnimations AppConfigVariable `key:"disableAnimations,public"` // Public
|
||||
AllowOwnAccountEdit AppConfigVariable `key:"allowOwnAccountEdit,public"` // Public
|
||||
// Internal
|
||||
BackgroundImageType AppConfigVariable `key:"backgroundImageType,internal"` // Internal
|
||||
|
||||
@@ -58,6 +58,7 @@ func (s *AppConfigService) getDefaultDbConfig() *model.AppConfig {
|
||||
AppName: model.AppConfigVariable{Value: "Pocket ID"},
|
||||
SessionDuration: model.AppConfigVariable{Value: "60"},
|
||||
EmailsVerified: model.AppConfigVariable{Value: "false"},
|
||||
DisableAnimations: model.AppConfigVariable{Value: "false"},
|
||||
AllowOwnAccountEdit: model.AppConfigVariable{Value: "true"},
|
||||
// Internal
|
||||
BackgroundImageType: model.AppConfigVariable{Value: "jpg"},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/base64"
|
||||
@@ -11,11 +12,8 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jws"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
"github.com/lestrrat-go/jwx/v3/jwt"
|
||||
@@ -40,11 +38,17 @@ const (
|
||||
// This may be omitted on non-admin tokens
|
||||
IsAdminClaim = "isAdmin"
|
||||
|
||||
// AccessTokenJWTType is the media type for access tokens
|
||||
AccessTokenJWTType = "AT+JWT"
|
||||
// TokenTypeClaim is the claim used to identify the type of token
|
||||
TokenTypeClaim = "type"
|
||||
|
||||
// IDTokenJWTType is the media type for ID tokens
|
||||
IDTokenJWTType = "ID+JWT"
|
||||
// OAuthAccessTokenJWTType identifies a JWT as an OAuth access token
|
||||
OAuthAccessTokenJWTType = "oauth-access-token" //nolint:gosec
|
||||
|
||||
// AccessTokenJWTType identifies a JWT as an access token used by Pocket ID
|
||||
AccessTokenJWTType = "access-token"
|
||||
|
||||
// IDTokenJWTType identifies a JWT as an ID token used by Pocket ID
|
||||
IDTokenJWTType = "id-token"
|
||||
|
||||
// Acceptable clock skew for verifying tokens
|
||||
clockSkew = time.Minute
|
||||
@@ -195,6 +199,11 @@ func (s *JwtService) GenerateAccessToken(user model.User) (string, error) {
|
||||
return "", fmt.Errorf("failed to set 'aud' claim in token: %w", err)
|
||||
}
|
||||
|
||||
err = SetTokenType(token, AccessTokenJWTType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set 'type' claim in token: %w", err)
|
||||
}
|
||||
|
||||
err = SetIsAdmin(token, user.IsAdmin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set 'isAdmin' claim in token: %w", err)
|
||||
@@ -218,6 +227,7 @@ func (s *JwtService) VerifyAccessToken(tokenString string) (jwt.Token, error) {
|
||||
jwt.WithAcceptableSkew(clockSkew),
|
||||
jwt.WithAudience(common.EnvConfig.AppURL),
|
||||
jwt.WithIssuer(common.EnvConfig.AppURL),
|
||||
jwt.WithValidator(TokenTypeValidator(AccessTokenJWTType)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse token: %w", err)
|
||||
@@ -242,6 +252,11 @@ func (s *JwtService) GenerateIDToken(userClaims map[string]any, clientID string,
|
||||
return "", fmt.Errorf("failed to set 'aud' claim in token: %w", err)
|
||||
}
|
||||
|
||||
err = SetTokenType(token, IDTokenJWTType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set 'type' claim in token: %w", err)
|
||||
}
|
||||
|
||||
for k, v := range userClaims {
|
||||
err = token.Set(k, v)
|
||||
if err != nil {
|
||||
@@ -256,13 +271,8 @@ func (s *JwtService) GenerateIDToken(userClaims map[string]any, clientID string,
|
||||
}
|
||||
}
|
||||
|
||||
headers, err := CreateTokenTypeHeader(IDTokenJWTType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set token type: %w", err)
|
||||
}
|
||||
|
||||
alg, _ := s.privateKey.Algorithm()
|
||||
signed, err := jwt.Sign(token, jwt.WithKey(alg, s.privateKey, jws.WithProtectedHeaders(headers)))
|
||||
signed, err := jwt.Sign(token, jwt.WithKey(alg, s.privateKey))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign token: %w", err)
|
||||
}
|
||||
@@ -281,6 +291,7 @@ func (s *JwtService) VerifyIdToken(tokenString string, acceptExpiredTokens bool)
|
||||
jwt.WithKey(alg, s.privateKey),
|
||||
jwt.WithAcceptableSkew(clockSkew),
|
||||
jwt.WithIssuer(common.EnvConfig.AppURL),
|
||||
jwt.WithValidator(TokenTypeValidator(IDTokenJWTType)),
|
||||
)
|
||||
|
||||
// By default, jwt.Parse includes 3 default validators for "nbf", "iat", and "exp"
|
||||
@@ -299,11 +310,6 @@ func (s *JwtService) VerifyIdToken(tokenString string, acceptExpiredTokens bool)
|
||||
return nil, fmt.Errorf("failed to parse token: %w", err)
|
||||
}
|
||||
|
||||
err = VerifyTokenTypeHeader(tokenString, IDTokenJWTType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to verify token type: %w", err)
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
@@ -324,13 +330,13 @@ func (s *JwtService) GenerateOauthAccessToken(user model.User, clientID string)
|
||||
return "", fmt.Errorf("failed to set 'aud' claim in token: %w", err)
|
||||
}
|
||||
|
||||
headers, err := CreateTokenTypeHeader(AccessTokenJWTType)
|
||||
err = SetTokenType(token, OAuthAccessTokenJWTType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set token type: %w", err)
|
||||
return "", fmt.Errorf("failed to set 'type' claim in token: %w", err)
|
||||
}
|
||||
|
||||
alg, _ := s.privateKey.Algorithm()
|
||||
signed, err := jwt.Sign(token, jwt.WithKey(alg, s.privateKey, jws.WithProtectedHeaders(headers)))
|
||||
signed, err := jwt.Sign(token, jwt.WithKey(alg, s.privateKey))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign token: %w", err)
|
||||
}
|
||||
@@ -346,16 +352,12 @@ func (s *JwtService) VerifyOauthAccessToken(tokenString string) (jwt.Token, erro
|
||||
jwt.WithKey(alg, s.privateKey),
|
||||
jwt.WithAcceptableSkew(clockSkew),
|
||||
jwt.WithIssuer(common.EnvConfig.AppURL),
|
||||
jwt.WithValidator(TokenTypeValidator(OAuthAccessTokenJWTType)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse token: %w", err)
|
||||
}
|
||||
|
||||
err = VerifyTokenTypeHeader(tokenString, AccessTokenJWTType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to verify token type: %w", err)
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
@@ -510,15 +512,12 @@ func GetIsAdmin(token jwt.Token) (bool, error) {
|
||||
return isAdmin, err
|
||||
}
|
||||
|
||||
// CreateTokenTypeHeader creates a new JWS header with the given token type
|
||||
func CreateTokenTypeHeader(tokenType string) (jws.Headers, error) {
|
||||
headers := jws.NewHeaders()
|
||||
err := headers.Set(jws.TypeKey, tokenType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to set token type: %w", err)
|
||||
// SetTokenType sets the "type" claim in the token
|
||||
func SetTokenType(token jwt.Token, tokenType string) error {
|
||||
if tokenType == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return headers, nil
|
||||
return token.Set(TokenTypeClaim, tokenType)
|
||||
}
|
||||
|
||||
// SetIsAdmin sets the "isAdmin" claim in the token
|
||||
@@ -536,36 +535,17 @@ func SetAudienceString(token jwt.Token, audience string) error {
|
||||
return token.Set(jwt.AudienceKey, audience)
|
||||
}
|
||||
|
||||
// VerifyTokenTypeHeader verifies that the "typ" header in the token matches the expected type
|
||||
func VerifyTokenTypeHeader(tokenBytes string, expectedTokenType string) error {
|
||||
// Parse the raw token string purely as a JWS message structure
|
||||
// We don't need to verify the signature at this stage, just inspect headers.
|
||||
msg, err := jws.Parse([]byte(tokenBytes))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse token as JWS message: %w", err)
|
||||
// TokenTypeValidator is a validator function that checks the "type" claim in the token
|
||||
func TokenTypeValidator(expectedTokenType string) jwt.ValidatorFunc {
|
||||
return func(_ context.Context, t jwt.Token) error {
|
||||
var tokenType string
|
||||
err := t.Get(TokenTypeClaim, &tokenType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get token type claim: %w", err)
|
||||
}
|
||||
if tokenType != expectedTokenType {
|
||||
return fmt.Errorf("invalid token type: expected %s, got %s", expectedTokenType, tokenType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the list of signatures attached to the message. Usually just one for JWT.
|
||||
signatures := msg.Signatures()
|
||||
if len(signatures) == 0 {
|
||||
return errors.New("JWS message contains no signatures")
|
||||
}
|
||||
|
||||
protectedHeaders := signatures[0].ProtectedHeaders()
|
||||
if protectedHeaders == nil {
|
||||
return fmt.Errorf("JWS signature has no protected headers")
|
||||
}
|
||||
|
||||
// Retrieve the 'typ' header value from the PROTECTED headers.
|
||||
var typHeaderValue string
|
||||
err = protectedHeaders.Get(jws.TypeKey, &typHeaderValue)
|
||||
if err != nil {
|
||||
return fmt.Errorf("token is missing required protected header '%s'", jws.TypeKey)
|
||||
}
|
||||
|
||||
if !strings.EqualFold(typHeaderValue, expectedTokenType) {
|
||||
return fmt.Errorf("'%s' header mismatch: expected '%s', got '%s'", jws.TypeKey, expectedTokenType, typHeaderValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jws"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
"github.com/lestrrat-go/jwx/v3/jwt"
|
||||
@@ -636,6 +634,9 @@ func TestGenerateVerifyIdToken(t *testing.T) {
|
||||
Build()
|
||||
require.NoError(t, err, "Failed to build token")
|
||||
|
||||
err = SetTokenType(token, IDTokenJWTType)
|
||||
require.NoError(t, err, "Failed to set token type")
|
||||
|
||||
// Add custom claims
|
||||
for k, v := range userClaims {
|
||||
if k != "sub" { // Already set above
|
||||
@@ -644,13 +645,8 @@ func TestGenerateVerifyIdToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Create headers with the specified type
|
||||
hdrs := jws.NewHeaders()
|
||||
err = hdrs.Set(jws.TypeKey, "ID+JWT")
|
||||
require.NoError(t, err, "Failed to set header type")
|
||||
|
||||
// Sign the token
|
||||
signed, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), service.privateKey, jws.WithProtectedHeaders(hdrs)))
|
||||
signed, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), service.privateKey))
|
||||
require.NoError(t, err, "Failed to sign token")
|
||||
tokenString := string(signed)
|
||||
|
||||
@@ -968,6 +964,9 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) {
|
||||
Build()
|
||||
require.NoError(t, err, "Failed to build token")
|
||||
|
||||
err = SetTokenType(token, OAuthAccessTokenJWTType)
|
||||
require.NoError(t, err, "Failed to set token type")
|
||||
|
||||
signed, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), service.privateKey))
|
||||
require.NoError(t, err, "Failed to sign token")
|
||||
|
||||
@@ -1168,59 +1167,50 @@ func TestGenerateVerifyOauthAccessToken(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyTokenTypeHeader(t *testing.T) {
|
||||
mockConfig := &AppConfigService{}
|
||||
tempDir := t.TempDir()
|
||||
// Helper function to create a token with a specific type header
|
||||
createTokenWithType := func(tokenType string) (string, error) {
|
||||
// Create a simple JWT token
|
||||
token := jwt.New()
|
||||
err := token.Set("test_claim", "test_value")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set claim: %w", err)
|
||||
}
|
||||
|
||||
// Create headers with the specified type
|
||||
hdrs := jws.NewHeaders()
|
||||
if tokenType != "" {
|
||||
err = hdrs.Set(jws.TypeKey, tokenType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to set type header: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Sign the token with the headers
|
||||
service := &JwtService{}
|
||||
err = service.init(mockConfig, tempDir)
|
||||
require.NoError(t, err, "Failed to initialize JWT service")
|
||||
|
||||
signed, err := jwt.Sign(token, jwt.WithKey(jwa.RS256(), service.privateKey, jws.WithProtectedHeaders(hdrs)))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign token: %w", err)
|
||||
}
|
||||
|
||||
return string(signed), nil
|
||||
}
|
||||
func TestTokenTypeValidator(t *testing.T) {
|
||||
// Create a context for the validator function
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("succeeds when token type matches expected type", func(t *testing.T) {
|
||||
// Create a token with "JWT" type
|
||||
tokenString, err := createTokenWithType("JWT")
|
||||
require.NoError(t, err, "Failed to create test token")
|
||||
// Create a token with the expected type
|
||||
token := jwt.New()
|
||||
err := token.Set(TokenTypeClaim, AccessTokenJWTType)
|
||||
require.NoError(t, err, "Failed to set token type claim")
|
||||
|
||||
// Verify the token type
|
||||
err = VerifyTokenTypeHeader(tokenString, "JWT")
|
||||
assert.NoError(t, err, "Should accept token with matching type")
|
||||
// Create a validator function for the expected type
|
||||
validator := TokenTypeValidator(AccessTokenJWTType)
|
||||
|
||||
// Validate the token
|
||||
err = validator(ctx, token)
|
||||
assert.NoError(t, err, "Validator should accept token with matching type")
|
||||
})
|
||||
|
||||
t.Run("fails when token type doesn't match expected type", func(t *testing.T) {
|
||||
// Create a token with "AT+JWT" type
|
||||
tokenString, err := createTokenWithType("AT+JWT")
|
||||
require.NoError(t, err, "Failed to create test token")
|
||||
// Create a token with a different type
|
||||
token := jwt.New()
|
||||
err := token.Set(TokenTypeClaim, OAuthAccessTokenJWTType)
|
||||
require.NoError(t, err, "Failed to set token type claim")
|
||||
|
||||
// Verify the token with different expected type
|
||||
err = VerifyTokenTypeHeader(tokenString, "JWT")
|
||||
require.Error(t, err, "Should reject token with non-matching type")
|
||||
assert.Contains(t, err.Error(), "header mismatch: expected 'JWT', got 'AT+JWT'")
|
||||
// Create a validator function for a different expected type
|
||||
validator := TokenTypeValidator(IDTokenJWTType)
|
||||
|
||||
// Validate the token
|
||||
err = validator(ctx, token)
|
||||
require.Error(t, err, "Validator should reject token with non-matching type")
|
||||
assert.Contains(t, err.Error(), "invalid token type: expected id-token, got oauth-access-token")
|
||||
})
|
||||
|
||||
t.Run("fails when token type claim is missing", func(t *testing.T) {
|
||||
// Create a token without a type claim
|
||||
token := jwt.New()
|
||||
|
||||
// Create a validator function
|
||||
validator := TokenTypeValidator(AccessTokenJWTType)
|
||||
|
||||
// Validate the token
|
||||
err := validator(ctx, token)
|
||||
require.Error(t, err, "Validator should reject token without type claim")
|
||||
assert.Contains(t, err.Error(), "failed to get token type claim")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -322,5 +322,7 @@
|
||||
"see_all_account_activities_from_the_last_3_months": "See all user activity for the last 3 months.",
|
||||
"token_sign_in": "Token Sign In",
|
||||
"client_authorization": "Client Authorization",
|
||||
"new_client_authorization": "New Client Authorization"
|
||||
"new_client_authorization": "New Client Authorization",
|
||||
"disable_animations": "Disable Animations",
|
||||
"turn_off_all_animations_throughout_the_admin_ui": "Turn off all animations throughout the Admin UI"
|
||||
}
|
||||
|
||||
292
frontend/package-lock.json
generated
292
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pocket-id-frontend",
|
||||
"version": "0.45.0",
|
||||
"version": "0.46.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pocket-id-frontend",
|
||||
"version": "0.45.0",
|
||||
"version": "0.46.0",
|
||||
"dependencies": {
|
||||
"@simplewebauthn/browser": "^13.1.0",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
@@ -17,6 +17,7 @@
|
||||
"jose": "^5.9.6",
|
||||
"lucide-svelte": "^0.487.0",
|
||||
"mode-watcher": "^0.5.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"sveltekit-superforms": "^2.23.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
@@ -31,10 +32,11 @@
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@sveltejs/adapter-auto": "^4.0.0",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/kit": "^2.16.1",
|
||||
"@sveltejs/kit": "^2.20.7",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/node": "^22.10.10",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"bits-ui": "^0.22.0",
|
||||
"cmdk-sv": "^0.0.19",
|
||||
"eslint": "^9.19.0",
|
||||
@@ -1407,9 +1409,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sveltejs/kit": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.16.1.tgz",
|
||||
"integrity": "sha512-2pF5sgGJx9brYZ/9nNDYnh5KX0JguPF14dnvvtf/MqrvlWrDj/e7Rk3LBJPecFLLK1GRs6ZniD24gFPqZm/NFw==",
|
||||
"version": "2.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.7.tgz",
|
||||
"integrity": "sha512-dVbLMubpJJSLI4OYB+yWYNHGAhgc2bVevWuBjDj8jFUXIJOAnLwYP3vsmtcgoxNGUXoq0rHS5f7MFCsryb6nzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.6.0",
|
||||
"cookie": "^0.6.0",
|
||||
@@ -1725,6 +1728,15 @@
|
||||
"undici-types": "~6.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qrcode": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz",
|
||||
"integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||
@@ -2036,11 +2048,19 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
@@ -2237,6 +2257,17 @@
|
||||
"validator": "^13.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@@ -2296,7 +2327,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
@@ -2307,8 +2337,7 @@
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
@@ -2439,6 +2468,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dedent": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz",
|
||||
@@ -2501,6 +2539,12 @@
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
|
||||
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw=="
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
@@ -2516,6 +2560,12 @@
|
||||
"fast-check": "^3.23.1"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz",
|
||||
@@ -3080,6 +3130,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -3223,6 +3282,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
@@ -3858,6 +3926,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
@@ -3874,7 +3951,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -3941,6 +4017,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
@@ -4227,6 +4312,22 @@
|
||||
],
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -4276,6 +4377,21 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
@@ -4398,6 +4514,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
@@ -4476,6 +4598,32 @@
|
||||
"kysely": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
@@ -5128,6 +5276,12 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
@@ -5137,6 +5291,26 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
||||
@@ -5150,6 +5324,102 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser/node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pocket-id-frontend",
|
||||
"version": "0.46.0",
|
||||
"version": "0.47.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -22,6 +22,7 @@
|
||||
"jose": "^5.9.6",
|
||||
"lucide-svelte": "^0.487.0",
|
||||
"mode-watcher": "^0.5.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"sveltekit-superforms": "^2.23.1",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
@@ -36,10 +37,11 @@
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@sveltejs/adapter-auto": "^4.0.0",
|
||||
"@sveltejs/adapter-node": "^5.2.12",
|
||||
"@sveltejs/kit": "^2.16.1",
|
||||
"@sveltejs/kit": "^2.20.7",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/node": "^22.10.10",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"bits-ui": "^0.22.0",
|
||||
"cmdk-sv": "^0.0.19",
|
||||
"eslint": "^9.19.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
let {
|
||||
@@ -12,7 +13,7 @@
|
||||
children: Snippet;
|
||||
} = $props();
|
||||
|
||||
let containerNode: HTMLElement;
|
||||
let containerNode: HTMLElement | null = $state(null);
|
||||
|
||||
$effect(() => {
|
||||
page.route;
|
||||
@@ -53,6 +54,10 @@
|
||||
</style>
|
||||
</svelte:head>
|
||||
|
||||
<div class="fade-wrapper" bind:this={containerNode}>
|
||||
{#if $appConfigStore.disableAnimations}
|
||||
{@render children()}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="fade-wrapper" bind:this={containerNode}>
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import CopyToClipboard from '$lib/components/copy-to-clipboard.svelte';
|
||||
import Qrcode from '$lib/components/qrcode/qrcode.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import Input from '$lib/components/ui/input/input.svelte';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import * as Select from '$lib/components/ui/select/index.js';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { mode } from 'mode-watcher';
|
||||
|
||||
let {
|
||||
userId = $bindable()
|
||||
@@ -18,6 +21,7 @@
|
||||
const userService = new UserService();
|
||||
|
||||
let oneTimeLink: string | null = $state(null);
|
||||
let code: string | null = $state(null);
|
||||
let selectedExpiration: keyof typeof availableExpirations = $state(m.one_hour());
|
||||
|
||||
let availableExpirations = {
|
||||
@@ -31,8 +35,8 @@
|
||||
async function createOneTimeAccessToken() {
|
||||
try {
|
||||
const expiration = new Date(Date.now() + availableExpirations[selectedExpiration] * 1000);
|
||||
const token = await userService.createOneTimeAccessToken(expiration, userId!);
|
||||
oneTimeLink = `${page.url.origin}/lc/${token}`;
|
||||
code = await userService.createOneTimeAccessToken(expiration, userId!);
|
||||
oneTimeLink = `${page.url.origin}/lc/${code}`;
|
||||
} catch (e) {
|
||||
axiosErrorToast(e);
|
||||
}
|
||||
@@ -41,6 +45,7 @@
|
||||
function onOpenChange(open: boolean) {
|
||||
if (!open) {
|
||||
oneTimeLink = null;
|
||||
code = null;
|
||||
userId = null;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +59,7 @@
|
||||
>{m.create_a_login_code_to_sign_in_without_a_passkey_once()}</Dialog.Description
|
||||
>
|
||||
</Dialog.Header>
|
||||
|
||||
{#if oneTimeLink === null}
|
||||
<div>
|
||||
<Label for="expiration">{m.expiration()}</Label>
|
||||
@@ -65,7 +71,7 @@
|
||||
onSelectedChange={(v) =>
|
||||
(selectedExpiration = v!.value as keyof typeof availableExpirations)}
|
||||
>
|
||||
<Select.Trigger class="h-9 ">
|
||||
<Select.Trigger class="h-9 w-full">
|
||||
<Select.Value>{selectedExpiration}</Select.Value>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
@@ -75,12 +81,36 @@
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<Button onclick={() => createOneTimeAccessToken()} disabled={!selectedExpiration}>
|
||||
<Button
|
||||
onclick={() => createOneTimeAccessToken()}
|
||||
disabled={!selectedExpiration}
|
||||
class="mt-2 w-full"
|
||||
>
|
||||
{m.generate_code()}
|
||||
</Button>
|
||||
{:else}
|
||||
<Label for="login-code" class="sr-only">{m.login_code()}</Label>
|
||||
<Input id="login-code" value={oneTimeLink} readonly />
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<CopyToClipboard value={code!}>
|
||||
<p class="text-3xl font-semibold">{code}</p>
|
||||
</CopyToClipboard>
|
||||
|
||||
<div class="text-muted-foreground my-2 flex items-center justify-center gap-3">
|
||||
<Separator />
|
||||
<p class="text-nowrap text-xs">{m.or_visit()}</p>
|
||||
<Separator />
|
||||
</div>
|
||||
|
||||
<Qrcode
|
||||
class="mb-2"
|
||||
value={oneTimeLink}
|
||||
size={180}
|
||||
color={$mode === 'dark' ? '#FFFFFF' : '#000000'}
|
||||
backgroundColor={$mode === 'dark' ? '#000000' : '#FFFFFF'}
|
||||
/>
|
||||
<CopyToClipboard value={oneTimeLink!}>
|
||||
<p data-testId="login-code-link">{oneTimeLink!}</p>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
{/if}
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
42
frontend/src/lib/components/qrcode/qrcode.svelte
Normal file
42
frontend/src/lib/components/qrcode/qrcode.svelte
Normal file
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils/style';
|
||||
import QRCode from 'qrcode';
|
||||
import { onMount } from 'svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let canvasEl: HTMLCanvasElement | null;
|
||||
let {
|
||||
value,
|
||||
size = 200,
|
||||
color = '#000000',
|
||||
backgroundColor = '#FFFFFF',
|
||||
...restProps
|
||||
}: HTMLAttributes<HTMLCanvasElement> & {
|
||||
value: string | null;
|
||||
size?: number;
|
||||
color?: string;
|
||||
backgroundColor?: string;
|
||||
} = $props();
|
||||
|
||||
onMount(() => {
|
||||
if (value && canvasEl) {
|
||||
// Convert "transparent" to a valid value for the QR code library
|
||||
const lightColor = backgroundColor === 'transparent' ? '#00000000' : backgroundColor;
|
||||
|
||||
const options = {
|
||||
width: size,
|
||||
margin: 0,
|
||||
color: {
|
||||
dark: color,
|
||||
light: lightColor
|
||||
}
|
||||
};
|
||||
|
||||
QRCode.toCanvas(canvasEl, value, options).catch((error: Error) => {
|
||||
console.error('Error generating QR Code:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<canvas {...restProps} bind:this={canvasEl} class={cn('rounded-lg', restProps.class)}></canvas>
|
||||
@@ -3,6 +3,7 @@ export type AppConfig = {
|
||||
allowOwnAccountEdit: boolean;
|
||||
emailOneTimeAccessEnabled: boolean;
|
||||
ldapEnabled: boolean;
|
||||
disableAnimations: boolean;
|
||||
};
|
||||
|
||||
export type AllAppConfig = AppConfig & {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<title>{m.sign_in()}</title>
|
||||
</svelte:head>
|
||||
|
||||
<SignInWrapper animate showAlternativeSignInMethodButton>
|
||||
<SignInWrapper animate={!$appConfigStore.disableAnimations} showAlternativeSignInMethodButton>
|
||||
<div class="flex justify-center">
|
||||
<LoginLogoErrorSuccessIndicator error={!!error} />
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<title>{m.sign_in()}</title>
|
||||
</svelte:head>
|
||||
|
||||
<SignInWrapper>
|
||||
<SignInWrapper animate={!$appConfigStore.disableAnimations}>
|
||||
<div class="flex h-full flex-col justify-center">
|
||||
<div class="bg-muted mx-auto rounded-2xl p-3">
|
||||
<Logo class="h-10 w-10" />
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<SignInWrapper>
|
||||
<SignInWrapper animate={!$appConfigStore.disableAnimations}>
|
||||
<div class="flex justify-center">
|
||||
<LoginLogoErrorSuccessIndicator error={!!error} />
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
import { page } from '$app/state';
|
||||
import FadeWrapper from '$lib/components/fade-wrapper.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import userStore from '$lib/stores/user-store';
|
||||
import { cn } from '$lib/utils/style';
|
||||
import { LucideExternalLink, LucideSettings } from 'lucide-svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
@@ -20,7 +22,7 @@
|
||||
|
||||
const links = [
|
||||
{ href: '/settings/account', label: m.my_account() },
|
||||
{ href: '/settings/audit-log', label: m.audit_log() },
|
||||
{ href: '/settings/audit-log', label: m.audit_log() }
|
||||
];
|
||||
|
||||
const adminLinks = [
|
||||
@@ -54,11 +56,12 @@
|
||||
{#each links as { href, label }, i}
|
||||
<a
|
||||
{href}
|
||||
class={`animate-fade-in ${
|
||||
class={cn(
|
||||
!$appConfigStore.disableAnimations && 'animate-fade-in',
|
||||
page.url.pathname.startsWith(href)
|
||||
? 'text-primary bg-card rounded-md px-3 py-1.5 font-medium shadow-sm transition-all'
|
||||
: 'hover:text-foreground hover:bg-muted/70 rounded-md px-3 py-1.5 transition-all hover:-translate-y-[2px] hover:shadow-sm'
|
||||
}`}
|
||||
)}
|
||||
style={`animation-delay: ${150 + i * 75}ms;`}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import CopyToClipboard from '$lib/components/copy-to-clipboard.svelte';
|
||||
import Qrcode from '$lib/components/qrcode/qrcode.svelte';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { mode } from 'mode-watcher';
|
||||
|
||||
let {
|
||||
show = $bindable()
|
||||
@@ -16,13 +18,17 @@
|
||||
const userService = new UserService();
|
||||
|
||||
let code: string | null = $state(null);
|
||||
let loginCodeLink: string | null = $state(null);
|
||||
|
||||
$effect(() => {
|
||||
if (show) {
|
||||
const expiration = new Date(Date.now() + 15 * 60 * 1000);
|
||||
userService
|
||||
.createOneTimeAccessToken(expiration, 'me')
|
||||
.then((c) => (code = c))
|
||||
.then((c) => {
|
||||
code = c;
|
||||
loginCodeLink = page.url.origin + '/lc/' + code;
|
||||
})
|
||||
.catch((e) => axiosErrorToast(e));
|
||||
}
|
||||
});
|
||||
@@ -48,16 +54,22 @@
|
||||
<CopyToClipboard value={code!}>
|
||||
<p class="text-3xl font-semibold">{code}</p>
|
||||
</CopyToClipboard>
|
||||
<div class="text-muted-foreground flex items-center justify-center gap-3">
|
||||
<div class="text-muted-foreground my-2 flex items-center justify-center gap-3">
|
||||
<Separator />
|
||||
<p class="text-nowrap text-xs">{m.or_visit()}</p>
|
||||
<Separator />
|
||||
</div>
|
||||
<div>
|
||||
<CopyToClipboard value={page.url.origin + '/lc/' + code!}>
|
||||
<p data-testId="login-code-link">{page.url.origin + '/lc/' + code!}</p>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
|
||||
<Qrcode
|
||||
class="mb-2"
|
||||
value={loginCodeLink}
|
||||
size={180}
|
||||
color={$mode === 'dark' ? '#FFFFFF' : '#000000'}
|
||||
backgroundColor={$mode === 'dark' ? '#000000' : '#FFFFFF'}
|
||||
/>
|
||||
<CopyToClipboard value={loginCodeLink!}>
|
||||
<p data-testId="login-code-link">{loginCodeLink!}</p>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -24,14 +24,16 @@
|
||||
appName: appConfig.appName,
|
||||
sessionDuration: appConfig.sessionDuration,
|
||||
emailsVerified: appConfig.emailsVerified,
|
||||
allowOwnAccountEdit: appConfig.allowOwnAccountEdit
|
||||
allowOwnAccountEdit: appConfig.allowOwnAccountEdit,
|
||||
disableAnimations: appConfig.disableAnimations
|
||||
};
|
||||
|
||||
const formSchema = z.object({
|
||||
appName: z.string().min(2).max(30),
|
||||
sessionDuration: z.number().min(1).max(43200),
|
||||
emailsVerified: z.boolean(),
|
||||
allowOwnAccountEdit: z.boolean()
|
||||
allowOwnAccountEdit: z.boolean(),
|
||||
disableAnimations: z.boolean()
|
||||
});
|
||||
|
||||
const { inputs, ...form } = createForm<typeof formSchema>(formSchema, updatedAppConfig);
|
||||
@@ -66,6 +68,12 @@
|
||||
description={m.whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients()}
|
||||
bind:checked={$inputs.emailsVerified.value}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="disable-animations"
|
||||
label={m.disable_animations()}
|
||||
description={m.turn_off_all_animations_throughout_the_admin_ui()}
|
||||
bind:checked={$inputs.disableAnimations.value}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-5 flex justify-end">
|
||||
<Button {isLoading} type="submit">{m.save()}</Button>
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
import { openConfirmDialog } from '$lib/components/confirm-dialog/';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import OIDCService from '$lib/services/oidc-service';
|
||||
import type { OidcClient } from '$lib/types/oidc.type';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
import { LucidePencil, LucideTrash } from 'lucide-svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import OneTimeLinkModal from './client-secret.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
clients = $bindable(),
|
||||
@@ -20,8 +19,6 @@
|
||||
requestOptions: SearchPaginationSortRequest;
|
||||
} = $props();
|
||||
|
||||
let oneTimeLink = $state<string | null>(null);
|
||||
|
||||
const oidcService = new OIDCService();
|
||||
|
||||
async function deleteClient(client: OidcClient) {
|
||||
@@ -86,5 +83,3 @@
|
||||
</Table.Cell>
|
||||
{/snippet}
|
||||
</AdvancedTable>
|
||||
|
||||
<OneTimeLinkModal {oneTimeLink} />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import test, { expect } from '@playwright/test';
|
||||
import { users } from './data';
|
||||
import authUtil from './utils/auth.util';
|
||||
import { cleanupBackend } from './utils/cleanup.util';
|
||||
import passkeyUtil from './utils/passkey.util';
|
||||
import authUtil from './utils/auth.util';
|
||||
|
||||
test.beforeEach(cleanupBackend);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cleanupBackend } from './utils/cleanup.util';
|
||||
|
||||
test.describe('API Key Management', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await cleanupBackend()
|
||||
await cleanupBackend();
|
||||
await page.goto('/settings/admin/api-keys');
|
||||
});
|
||||
|
||||
|
||||
@@ -88,13 +88,13 @@ export const refreshTokens = [
|
||||
export const idTokens = [
|
||||
{
|
||||
token:
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiaWQrand0In0.eyJhdWQiOiIzNjU0YTc0Ni0zNWQ0LTQzMjEtYWM2MS0wYmRjZmYyYjQwNTUiLCJlbWFpbCI6InRpbS5jb29rQHRlc3QuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTY5MDAwMDAwMSwiZmFtaWx5X25hbWUiOiJUaW0iLCJnaXZlbl9uYW1lIjoiQ29vayIsImlhdCI6MTY5MDAwMDAwMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdCIsIm5hbWUiOiJUaW0gQ29vayIsIm5vbmNlIjoib1cxQTFPNzhHUTE1RDczT3NIRXg3V1FLajdacXZITFp1XzM3bWRYSXFBUSIsInN1YiI6IjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIn0.LHwNnp9WxFc_NbIVsBC41trA-1LUBxTfKwIqfgGP4WC5j39M2Rmc0G4rw7J96tfwyEobwgPFAP0YJ3BqMaZgHT4Zu0rYSenU-yv_CICWiLL4csyeojlqbqDKDiOD3Gsl4_ZUuo8UuN190RGz6HlxmTwxpmceerSFpx6dBtA6chYZfgnUf289DRWIgTsNrXnkohZRa8zWc8bjbw_hj1u7H6Ev9Yu3U2k4K0cHWZLFjQiPWt3JBaWNAldSEn2q7a3Rkyv17_Gx8Nwl5L4ugWKV8M1YkcHbEkYCJKaJCbZi13R89yH1E0EOfHYXK5Z0KqBq47eTYRGRUtFiP-uTlUDQUQ',
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiSldUIn0.eyJhdWQiOiIzNjU0YTc0Ni0zNWQ0LTQzMjEtYWM2MS0wYmRjZmYyYjQwNTUiLCJlbWFpbCI6InRpbS5jb29rQHRlc3QuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MTY5MDAwMDAwMSwiZmFtaWx5X25hbWUiOiJUaW0iLCJnaXZlbl9uYW1lIjoiQ29vayIsImlhdCI6MTY5MDAwMDAwMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdCIsIm5hbWUiOiJUaW0gQ29vayIsIm5vbmNlIjoib1cxQTFPNzhHUTE1RDczT3NIRXg3V1FLajdacXZITFp1XzM3bWRYSXFBUSIsInN1YiI6IjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIiwidHlwZSI6ImlkLXRva2VuIn0.noxQ-sCNHh7f8EaySJT7oF0DlmjYcM-FdMPH45Yuuvt5-bTpLLkggN9aq8RILmkGL9xUVsfZbYkWV5EkGobxfIoXITE98xH54BQwtpOjLL_HZLF4kFXarUyGLGO3zeVJAQzyofVz_1rKfDlZdi5Zmm-91cO5OiOtshfluDqt1h1D-E5h4ShT0eN7apvSvQnD7806-3tfxP0GHE-HuerR1Qbv9p0uWmuhT0CkVIM-K2dKBHdhLtquRqxNp2EuD_T-HA3WJgvkTTWp-JZ6NqvWDMy3M-jB-_Bs9eABERlTSTp7H2XCMGbwRSBZDmSn-97LPwc-NO5JYEkgZOeVr_r6qg',
|
||||
clientId: oidcClients.nextcloud.id,
|
||||
expired: true
|
||||
},
|
||||
{
|
||||
token:
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiaWQrand0In0.eyJhdWQiOiIzNjU0YTc0Ni0zNWQ0LTQzMjEtYWM2MS0wYmRjZmYyYjQwNTUiLCJlbWFpbCI6InRpbS5jb29rQHRlc3QuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MjY5MDAwMDAwMSwiZmFtaWx5X25hbWUiOiJUaW0iLCJnaXZlbl9uYW1lIjoiQ29vayIsImlhdCI6MTY5MDAwMDAwMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdCIsIm5hbWUiOiJUaW0gQ29vayIsIm5vbmNlIjoib1cxQTFPNzhHUTE1RDczT3NIRXg3V1FLajdacXZITFp1XzM3bWRYSXFBUSIsInN1YiI6IjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIn0.yG21sES1HMyQg6GeJtd-6sUJ5a_QBS-hHq3mDTjRoMkL604RxprPvIJ-ypYhzcV5LwlTiD-7jJQ2Z95uUb82aNek55V5Pzq_rcLM5EtHh2bHSegt_1QXcpBzl8mWB1AIZBSRzFDaB1msnkyxGnndJk4VHpUVStvubcldxksH3e9v286x9ak4oTNoaLy4kMi4KAE8WCwrqsYc1iieLOSFTRHjpM9YxWa8X9hGNsikC85NJ0tj1pG9I4QTG62h4ZqJ4-jFWe5dogg_vd9Sk7tA3f9S779XSCG6hpj1V-sxQqLCy9uAmB2URP4N60jamKTn2TCxc1R7xgQ7M9Rc9ty68g',
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiSldUIn0.eyJhdWQiOiIzNjU0YTc0Ni0zNWQ0LTQzMjEtYWM2MS0wYmRjZmYyYjQwNTUiLCJlbWFpbCI6InRpbS5jb29rQHRlc3QuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImV4cCI6MjY5MDAwMDAwMSwiZmFtaWx5X25hbWUiOiJUaW0iLCJnaXZlbl9uYW1lIjoiQ29vayIsImlhdCI6MTY5MDAwMDAwMCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdCIsIm5hbWUiOiJUaW0gQ29vayIsIm5vbmNlIjoib1cxQTFPNzhHUTE1RDczT3NIRXg3V1FLajdacXZITFp1XzM3bWRYSXFBUSIsInN1YiI6IjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIiwidHlwZSI6ImlkLXRva2VuIn0.ry7s3xP4-vvvZPzcRCvR1yBl32Pi09ryC6Z-67E1P4xChe8MaMoiQgImS5ZNbZiYzBN4cdkQsExXZK1FP-kMD019k3uNKPq0fIREBwrT9wXPqQJlLSBmN-tVkjLm90-b310SG5p65aajWvMkcPmJleG6y24_zoPFr3ISGI87vV6zdyoqG55pc-GkT7FwiEFIZJGQAzl7u1uOi7sQrda8Y6rF_SCC-f9I4PnHblnaTne8pfXe9jXKJeY1ZKj2Qh9dRPhWCLPHHV1YErUyoMP9oeMVzYpno-pBYVOiT9Ktl6CpG-jqB8smKqDEhZrSejgZ256h34f8jNL1SEhpM-4_cQ',
|
||||
clientId: oidcClients.nextcloud.id,
|
||||
expired: false
|
||||
}
|
||||
@@ -103,13 +103,13 @@ export const idTokens = [
|
||||
export const accessTokens = [
|
||||
{
|
||||
token:
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiYXQrand0In0.eyJhdWQiOlsiMzY1NGE3NDYtMzVkNC00MzIxLWFjNjEtMGJkY2ZmMmI0MDU1Il0sImV4cCI6MTc0Mzk3MjI4MywiaWF0IjoxNzQzOTY4NjgzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Iiwic3ViIjoiZjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIn0.iwkQR96BKTJugh87_YOrDb7hXSWsv0RJXrqrqxHn3rwhcKNxwGnYAhTiQ12wyi-77-AFkzUlgs9E9pwgVi3_sE37QCVZ3YZzHjbg5crmT1EJ4f8gN8hw5cDqC3ny0R8rhgNzzirpZNe-i7SXzWCIySyEVh7MGFTPqNA-1ZlGh06FuOFRb22GVaHfrDkpE2RhkeZ-ZLlua9pbTcT1T9CihlCrW8JKTUwT2QspCwtnaJGs34iH77sHry31cTYVyOqd5q218tg_N4ky9iV6k7mK6b7uaPsjYHrtpfK1tp-9_MSp6Fqzw6wu_vrvg5WrIWwiREaz_wJj-SjIuBR5TlntdA',
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiSldUIn0.eyJhdWQiOlsiMzY1NGE3NDYtMzVkNC00MzIxLWFjNjEtMGJkY2ZmMmI0MDU1Il0sImV4cCI6MTc0Mzk3MjI4MywiaWF0IjoxNzQzOTY4NjgzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Iiwic3ViIjoiZjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIiwidHlwZSI6Im9hdXRoLWFjY2Vzcy10b2tlbiJ9.REFSDFsGso9u7WxpyMmMVvjQMgulbidQNUft-kBRg7nw5LN9pOWhO0Zlr1tZnnrA1LenZRv0BvLIf0qekwGEC4FOPmJ6-As2ggIcoBIXpUR2A4Hhuy0FtqbCUgIkda1Dcx9w1Rmfzi0eHY_-1H_98rDgS5RxqweNA_YP3RsnJqBsc9GYhDarrf1nyCOplshGOEiyisUGoU2TaURI6DTcCiDzVOm_esZqokoZTpKlQw6ZugDDObro0eWYgROo97_3cqPRgRjSYBYRAGCHhZom3bFkjmz3wqpeoGmUNgL022x3-gl7QjurpJMQrKJ7wkFs0bh2uFnnngnh2w6m4j8-5w',
|
||||
clientId: oidcClients.nextcloud.id,
|
||||
expired: true
|
||||
},
|
||||
{
|
||||
token:
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiYXQrand0In0.eyJhdWQiOlsiMzY1NGE3NDYtMzVkNC00MzIxLWFjNjEtMGJkY2ZmMmI0MDU1Il0sImV4cCI6Mjc0Mzk3MjI4MywiaWF0IjoxNzQzOTY4NjgzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Iiwic3ViIjoiZjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIn0.lZMEohQeOi6oKDsKLKDDRYJIJNedUilvCLCi6XLADcHPtKlFJbPqH8IuQxuzryeIYAnTILsjvTkxkHAeRoQZCXQR7oS5BguGx6MtQYjgj--GpLBQ39r_nz-SEfhKtuMzEzPsN1raxOH8jWbnPM7zHxf5NIz7AHDKtCSWRA3JlE9kgAU7S-RRc6xP_BYVPDB97J6k-xuO5zxcdNTb92j8pZWbPPokv6CGG9CTPNzcrNHf-M98M6GE8SVM-8R2MAbpUCqTkTc_O46GHEexZzif2Wg8K5O-htiSQnwumoXXN08zKHCzCAvSdSa9JRMB-cgP7jsM7I6itUBXWxgvWDK3rA',
|
||||
'eyJhbGciOiJSUzI1NiIsImtpZCI6Ijh1SER3M002cmY4IiwidHlwIjoiSldUIn0.eyJhdWQiOlsiMzY1NGE3NDYtMzVkNC00MzIxLWFjNjEtMGJkY2ZmMmI0MDU1Il0sImV4cCI6Mjc0Mzk3MjI4MywiaWF0IjoxNzQzOTY4NjgzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Iiwic3ViIjoiZjRiODlkYzItNjJmYi00NmJmLTlmNWYtYzM0ZjRlYWZlOTNlIiwidHlwZSI6Im9hdXRoLWFjY2Vzcy10b2tlbiJ9.FaFsHJS_8wbvQvctftNTPyzAe9IhbpJiHIkhg28RrFRFfnBMq0QycmTUh00MJPXkUfd_j5tcCnXybF1efHsq6WbP4AWFG_TJMUyz7a9SYt1lGR8dxo3eys0YAX5eJQ5YoVTKNrivSKrC37Rg3VlcZVWXp6KBAxRWVl3OUlquSC6q7HNKAKg8sbBJiGpUJ37wwanOTE2XhYGvFB2_gxS36tvOuSTV3CVg_7Fctej7gNhKMXBFMJiIFurxZaeNud8620xtv-vJX6ALa1Qu1SkWhhZN2Yx3WuODZNlni3rUps-THoEdqh62jNwItE9wB7C0fGEKuUqVIllaF9I_7i2s3w',
|
||||
clientId: oidcClients.nextcloud.id,
|
||||
expired: false
|
||||
}
|
||||
|
||||
@@ -50,11 +50,13 @@ test('Create user fails with already taken username', async ({ page }) => {
|
||||
await expect(page.getByRole('status')).toHaveText('Username is already in use');
|
||||
});
|
||||
|
||||
test('Create one time access token', async ({ page }) => {
|
||||
test('Create one time access token', async ({ page, context }) => {
|
||||
await page.goto('/settings/admin/users');
|
||||
|
||||
await page
|
||||
.getByRole('row', { name: `${users.craig.firstname} ${users.craig.lastname}` })
|
||||
.getByRole('row', {
|
||||
name: `${users.craig.firstname} ${users.craig.lastname}`
|
||||
})
|
||||
.getByRole('button')
|
||||
.click();
|
||||
|
||||
@@ -64,16 +66,20 @@ test('Create one time access token', async ({ page }) => {
|
||||
await page.getByRole('option', { name: '12 hours' }).click();
|
||||
await page.getByRole('button', { name: 'Generate Code' }).click();
|
||||
|
||||
await expect(page.getByRole('textbox', { name: 'Login Code' })).toHaveValue(
|
||||
/http:\/\/localhost\/lc\/.*/
|
||||
);
|
||||
const link = await page.getByTestId('login-code-link').textContent();
|
||||
await context.clearCookies();
|
||||
|
||||
await page.goto(link!);
|
||||
await page.waitForURL('/settings/account');
|
||||
});
|
||||
|
||||
test('Delete user', async ({ page }) => {
|
||||
await page.goto('/settings/admin/users');
|
||||
|
||||
await page
|
||||
.getByRole('row', { name: `${users.craig.firstname} ${users.craig.lastname}` })
|
||||
.getByRole('row', {
|
||||
name: `${users.craig.firstname} ${users.craig.lastname}`
|
||||
})
|
||||
.getByRole('button')
|
||||
.click();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
@@ -81,7 +87,9 @@ test('Delete user', async ({ page }) => {
|
||||
|
||||
await expect(page.getByRole('status')).toHaveText('User deleted successfully');
|
||||
await expect(
|
||||
page.getByRole('row', { name: `${users.craig.firstname} ${users.craig.lastname}` })
|
||||
page.getByRole('row', {
|
||||
name: `${users.craig.firstname} ${users.craig.lastname}`
|
||||
})
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user