You've already forked pocketbase
							
							
				mirror of
				https://github.com/pocketbase/pocketbase.git
				synced 2025-10-31 16:47:43 +02:00 
			
		
		
		
	aliased and soft-deprecated NewToken with NewJWT, added encrypt/decrypt goja bindings and other minor doc changes
This commit is contained in:
		| @@ -71,6 +71,8 @@ | ||||
|  | ||||
| - Fixed `migrate down` not returning the correct `lastAppliedMigrations()` when the stored migration applied time is in seconds. | ||||
|  | ||||
| - Soft-deprecated `security.NewToken()` in favor of `security.NewJWT()`. | ||||
|  | ||||
|  | ||||
| ## v0.16.6 | ||||
|  | ||||
|   | ||||
| @@ -1,19 +1,3 @@ | ||||
| // Package jsvm implements optional utilities for binding a JS goja runtime | ||||
| // to the PocketBase instance (loading migrations, attaching to app hooks, etc.). | ||||
| // | ||||
| // Currently it provides the following plugins: | ||||
| // | ||||
| // 1. JS Migrations loader: | ||||
| // | ||||
| //	jsvm.MustRegisterMigrations(app, jsvm.MigrationsConfig{ | ||||
| //		Dir: "/custom/js/migrations/dir", // default to "pb_data/../pb_migrations" | ||||
| //	}) | ||||
| // | ||||
| // 2. JS app hooks: | ||||
| // | ||||
| //	jsvm.MustRegisterHooks(app, jsvm.HooksConfig{ | ||||
| //		Dir: "/custom/js/hooks/dir", // default to "pb_data/../pb_hooks" | ||||
| //	}) | ||||
| package jsvm | ||||
|  | ||||
| import ( | ||||
| @@ -216,9 +200,21 @@ func securityBinds(vm *goja.Runtime) { | ||||
| 	obj.Set("pseudorandomStringWithAlphabet", security.PseudorandomStringWithAlphabet) | ||||
|  | ||||
| 	// jwt | ||||
| 	obj.Set("parseUnverifiedToken", security.ParseUnverifiedJWT) | ||||
| 	obj.Set("parseToken", security.ParseJWT) | ||||
| 	obj.Set("createToken", security.NewToken) | ||||
| 	obj.Set("parseUnverifiedJWT", security.ParseUnverifiedJWT) | ||||
| 	obj.Set("parseJWT", security.ParseJWT) | ||||
| 	obj.Set("createJWT", security.NewJWT) | ||||
|  | ||||
| 	// encryption | ||||
| 	obj.Set("encrypt", security.Encrypt) | ||||
| 	obj.Set("decrypt", func(cipherText, key string) (string, error) { | ||||
| 		result, err := security.Decrypt(cipherText, key) | ||||
|  | ||||
| 		if err != nil { | ||||
| 			return "", err | ||||
| 		} | ||||
|  | ||||
| 		return string(result), err | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func filesystemBinds(vm *goja.Runtime) { | ||||
|   | ||||
| @@ -430,6 +430,13 @@ func TestDbxBinds(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTokensBindsCount(t *testing.T) { | ||||
| 	vm := goja.New() | ||||
| 	tokensBinds(vm) | ||||
|  | ||||
| 	testBindsCount(vm, "$tokens", 8, t) | ||||
| } | ||||
|  | ||||
| func TestTokensBinds(t *testing.T) { | ||||
| 	app, _ := tests.NewTestApp() | ||||
| 	defer app.Cleanup() | ||||
| @@ -451,8 +458,6 @@ func TestTokensBinds(t *testing.T) { | ||||
| 	baseBinds(vm) | ||||
| 	tokensBinds(vm) | ||||
|  | ||||
| 	testBindsCount(vm, "$tokens", 8, t) | ||||
|  | ||||
| 	sceneraios := []struct { | ||||
| 		js  string | ||||
| 		key string | ||||
| @@ -505,6 +510,13 @@ func TestTokensBinds(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSecurityBindsCount(t *testing.T) { | ||||
| 	vm := goja.New() | ||||
| 	securityBinds(vm) | ||||
|  | ||||
| 	testBindsCount(vm, "$security", 9, t) | ||||
| } | ||||
|  | ||||
| func TestSecurityRandomStringBinds(t *testing.T) { | ||||
| 	app, _ := tests.NewTestApp() | ||||
| 	defer app.Cleanup() | ||||
| @@ -513,8 +525,6 @@ func TestSecurityRandomStringBinds(t *testing.T) { | ||||
| 	baseBinds(vm) | ||||
| 	securityBinds(vm) | ||||
|  | ||||
| 	testBindsCount(vm, "$security", 7, t) | ||||
|  | ||||
| 	sceneraios := []struct { | ||||
| 		js     string | ||||
| 		length int | ||||
| @@ -539,7 +549,7 @@ func TestSecurityRandomStringBinds(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSecurityTokenBinds(t *testing.T) { | ||||
| func TestSecurityJWTBinds(t *testing.T) { | ||||
| 	app, _ := tests.NewTestApp() | ||||
| 	defer app.Cleanup() | ||||
|  | ||||
| @@ -547,22 +557,20 @@ func TestSecurityTokenBinds(t *testing.T) { | ||||
| 	baseBinds(vm) | ||||
| 	securityBinds(vm) | ||||
|  | ||||
| 	testBindsCount(vm, "$security", 7, t) | ||||
|  | ||||
| 	sceneraios := []struct { | ||||
| 		js       string | ||||
| 		expected string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			`$security.parseUnverifiedToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.aXzC7q7z1lX_hxk5P0R368xEU7H1xRwnBQQcLAmG0EY")`, | ||||
| 			`$security.parseUnverifiedJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.aXzC7q7z1lX_hxk5P0R368xEU7H1xRwnBQQcLAmG0EY")`, | ||||
| 			`{"name":"John Doe","sub":"1234567890"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			`$security.parseToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.aXzC7q7z1lX_hxk5P0R368xEU7H1xRwnBQQcLAmG0EY", "test")`, | ||||
| 			`$security.parseJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.aXzC7q7z1lX_hxk5P0R368xEU7H1xRwnBQQcLAmG0EY", "test")`, | ||||
| 			`{"name":"John Doe","sub":"1234567890"}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			`$security.createToken({"exp": 123}, "test", 0)`, // overwrite the exp claim for static token | ||||
| 			`$security.createJWT({"exp": 123}, "test", 0)`, // overwrite the exp claim for static token | ||||
| 			`"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEyM30.7gbv7w672gApdBRASI6OniCtKwkKjhieSxsr6vxSrtw"`, | ||||
| 		}, | ||||
| 	} | ||||
| @@ -581,6 +589,30 @@ func TestSecurityTokenBinds(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSecurityEncryptAndDecryptBinds(t *testing.T) { | ||||
| 	app, _ := tests.NewTestApp() | ||||
| 	defer app.Cleanup() | ||||
|  | ||||
| 	vm := goja.New() | ||||
| 	baseBinds(vm) | ||||
| 	securityBinds(vm) | ||||
|  | ||||
| 	_, err := vm.RunString(` | ||||
| 		const key = "abcdabcdabcdabcdabcdabcdabcdabcd" | ||||
|  | ||||
| 		const encrypted = $security.encrypt("123", key) | ||||
|  | ||||
| 		const decrypted = $security.decrypt(encrypted, key) | ||||
|  | ||||
| 		if (decrypted != "123") { | ||||
| 			throw new Error("Expected decrypted '123', got " + decrypted) | ||||
| 		} | ||||
| 	`) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestFilesystemBinds(t *testing.T) { | ||||
| 	app, _ := tests.NewTestApp() | ||||
| 	defer app.Cleanup() | ||||
|   | ||||
| @@ -60,7 +60,6 @@ declare class DynamicModel { | ||||
|   constructor(shape?: { [key:string]: any }) | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Record model class. | ||||
|  * | ||||
| @@ -223,6 +222,9 @@ declare class Dao implements daos.Dao { | ||||
| // ------------------------------------------------------------------- | ||||
|  | ||||
| /** | ||||
|  * $dbx defines common utility for working with the DB abstraction. | ||||
|  * For examples and guides please check the [Database guide](https://pocketbase.io/docs/js-database). | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare namespace $dbx { | ||||
| @@ -254,6 +256,11 @@ declare namespace $dbx { | ||||
| // ------------------------------------------------------------------- | ||||
|  | ||||
| /** | ||||
|  * ` + "`" + `$tokens` + "`" + ` defines high level helpers to generate | ||||
|  * various admins and auth records tokens (auth, forgotten password, etc.). | ||||
|  * | ||||
|  * For more control over the generated token, you can check ` + "`" + `$security` + "`" + `. | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare namespace $tokens { | ||||
| @@ -272,6 +279,9 @@ declare namespace $tokens { | ||||
| // ------------------------------------------------------------------- | ||||
|  | ||||
| /** | ||||
|  * ` + "`" + `$security` + "`" + ` defines low level helpers for creating | ||||
|  * and parsing JWTs, random string generation, AES encryption, etc. | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare namespace $security { | ||||
| @@ -279,9 +289,11 @@ declare namespace $security { | ||||
|   let randomStringWithAlphabet:       security.randomStringWithAlphabet | ||||
|   let pseudorandomString:             security.pseudorandomString | ||||
|   let pseudorandomStringWithAlphabet: security.pseudorandomStringWithAlphabet | ||||
|   let parseUnverifiedToken:           security.parseUnverifiedJWT | ||||
|   let parseToken:                     security.parseJWT | ||||
|   let createToken:                    security.newToken | ||||
|   let parseUnverifiedJWT:             security.parseUnverifiedJWT | ||||
|   let parseJWT:                       security.parseJWT | ||||
|   let createJWT:                      security.newJWT | ||||
|   let encrypt:                        security.encrypt | ||||
|   let decrypt:                        security.decrypt | ||||
| } | ||||
|  | ||||
| // ------------------------------------------------------------------- | ||||
| @@ -289,6 +301,9 @@ declare namespace $security { | ||||
| // ------------------------------------------------------------------- | ||||
|  | ||||
| /** | ||||
|  * ` + "`" + `$filesystem` + "`" + ` defines common helpers for working | ||||
|  * with the PocketBase filesystem abstraction. | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare namespace $filesystem { | ||||
| @@ -506,6 +521,8 @@ declare class Route implements echo.Route { | ||||
|  | ||||
| interface ApiError extends apis.ApiError{} // merge | ||||
| /** | ||||
|  * @inheritDoc | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare class ApiError implements apis.ApiError { | ||||
| @@ -514,6 +531,8 @@ declare class ApiError implements apis.ApiError { | ||||
|  | ||||
| interface NotFoundError extends apis.ApiError{} // merge | ||||
| /** | ||||
|  * NotFounderor returns 404 ApiError. | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare class NotFoundError implements apis.ApiError { | ||||
| @@ -522,6 +541,8 @@ declare class NotFoundError implements apis.ApiError { | ||||
|  | ||||
| interface BadRequestError extends apis.ApiError{} // merge | ||||
| /** | ||||
|  * BadRequestError returns 400 ApiError. | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare class BadRequestError implements apis.ApiError { | ||||
| @@ -530,6 +551,8 @@ declare class BadRequestError implements apis.ApiError { | ||||
|  | ||||
| interface ForbiddenError extends apis.ApiError{} // merge | ||||
| /** | ||||
|  * ForbiddenError returns 403 ApiError. | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare class ForbiddenError implements apis.ApiError { | ||||
| @@ -538,6 +561,8 @@ declare class ForbiddenError implements apis.ApiError { | ||||
|  | ||||
| interface UnauthorizedError extends apis.ApiError{} // merge | ||||
| /** | ||||
|  * UnauthorizedError returns 401 ApiError. | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare class UnauthorizedError implements apis.ApiError { | ||||
| @@ -545,6 +570,8 @@ declare class UnauthorizedError implements apis.ApiError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ` + "`" + `$apis` + "`" + ` defines commonly used PocketBase api helpers and middlewares. | ||||
|  * | ||||
|  * @group PocketBase | ||||
|  */ | ||||
| declare namespace $apis { | ||||
|   | ||||
							
								
								
									
										10026
									
								
								plugins/jsvm/internal/docs/generated/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10026
									
								
								plugins/jsvm/internal/docs/generated/types.d.ts
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,3 +1,11 @@ | ||||
| // Package jsvm implements pluggable utilities for binding a JS goja runtime | ||||
| // to the PocketBase instance (loading migrations, attaching to app hooks, etc.). | ||||
| // | ||||
| // Example: | ||||
| // | ||||
| //	jsvm.MustRegister(app, jsvm.Config{ | ||||
| //		WatchHooks: true, | ||||
| //	}) | ||||
| package jsvm | ||||
|  | ||||
| import ( | ||||
| @@ -30,21 +38,21 @@ const ( | ||||
|  | ||||
| // Config defines the config options of the jsvm plugin. | ||||
| type Config struct { | ||||
| 	// MigrationsDir specifies the JS migrations directory. | ||||
| 	// HooksWatch enables auto app restarts when a JS app hook file changes. | ||||
| 	// | ||||
| 	// If not set it fallbacks to a relative "pb_data/../pb_migrations" directory. | ||||
| 	MigrationsDir string | ||||
| 	// Note that currently the application cannot be automatically restarted on Windows | ||||
| 	// because the restart process relies on execve. | ||||
| 	HooksWatch bool | ||||
|  | ||||
| 	// HooksDir specifies the JS app hooks directory. | ||||
| 	// | ||||
| 	// If not set it fallbacks to a relative "pb_data/../pb_hooks" directory. | ||||
| 	HooksDir string | ||||
|  | ||||
| 	// HooksWatch enables auto app restarts when a JS app hook file changes. | ||||
| 	// MigrationsDir specifies the JS migrations directory. | ||||
| 	// | ||||
| 	// Note that currently the application cannot be automatically restarted on Windows | ||||
| 	// because the restart process relies on execve. | ||||
| 	HooksWatch bool | ||||
| 	// If not set it fallbacks to a relative "pb_data/../pb_migrations" directory. | ||||
| 	MigrationsDir string | ||||
|  | ||||
| 	// TypesDir specifies the directory where to store the embedded | ||||
| 	// TypeScript declarations file. | ||||
| @@ -125,7 +133,6 @@ func (p *plugin) registerMigrations() error { | ||||
| 		baseBinds(vm) | ||||
| 		dbxBinds(vm) | ||||
| 		filesystemBinds(vm) | ||||
| 		tokensBinds(vm) | ||||
| 		securityBinds(vm) | ||||
|  | ||||
| 		vm.Set("migrate", func(up, down func(db dbx.Builder) error) { | ||||
| @@ -176,7 +183,6 @@ func (p *plugin) registerHooks() error { | ||||
| 		baseBinds(vm) | ||||
| 		dbxBinds(vm) | ||||
| 		filesystemBinds(vm) | ||||
| 		tokensBinds(vm) | ||||
| 		securityBinds(vm) | ||||
| 		formsBinds(vm) | ||||
| 		apisBinds(vm) | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
| //	}) | ||||
| // | ||||
| //	Note: To allow running JS migrations you'll need to enable first | ||||
| //	[jsvm.MustRegisterMigrations]. | ||||
| //	[jsvm.MustRegister()]. | ||||
| package migratecmd | ||||
|  | ||||
| import ( | ||||
|   | ||||
| @@ -9,7 +9,7 @@ import ( | ||||
|  | ||||
| // NewAdminAuthToken generates and returns a new admin authentication token. | ||||
| func NewAdminAuthToken(app core.App, admin *models.Admin) (string, error) { | ||||
| 	return security.NewToken( | ||||
| 	return security.NewJWT( | ||||
| 		jwt.MapClaims{"id": admin.Id, "type": TypeAdmin}, | ||||
| 		(admin.TokenKey + app.Settings().AdminAuthToken.Secret), | ||||
| 		app.Settings().AdminAuthToken.Duration, | ||||
| @@ -18,7 +18,7 @@ func NewAdminAuthToken(app core.App, admin *models.Admin) (string, error) { | ||||
|  | ||||
| // NewAdminResetPasswordToken generates and returns a new admin password reset request token. | ||||
| func NewAdminResetPasswordToken(app core.App, admin *models.Admin) (string, error) { | ||||
| 	return security.NewToken( | ||||
| 	return security.NewJWT( | ||||
| 		jwt.MapClaims{"id": admin.Id, "type": TypeAdmin, "email": admin.Email}, | ||||
| 		(admin.TokenKey + app.Settings().AdminPasswordResetToken.Secret), | ||||
| 		app.Settings().AdminPasswordResetToken.Duration, | ||||
| @@ -27,7 +27,7 @@ func NewAdminResetPasswordToken(app core.App, admin *models.Admin) (string, erro | ||||
|  | ||||
| // NewAdminFileToken generates and returns a new admin private file access token. | ||||
| func NewAdminFileToken(app core.App, admin *models.Admin) (string, error) { | ||||
| 	return security.NewToken( | ||||
| 	return security.NewJWT( | ||||
| 		jwt.MapClaims{"id": admin.Id, "type": TypeAdmin}, | ||||
| 		(admin.TokenKey + app.Settings().AdminFileToken.Secret), | ||||
| 		app.Settings().AdminFileToken.Duration, | ||||
|   | ||||
| @@ -15,7 +15,7 @@ func NewRecordAuthToken(app core.App, record *models.Record) (string, error) { | ||||
| 		return "", errors.New("The record is not from an auth collection.") | ||||
| 	} | ||||
|  | ||||
| 	return security.NewToken( | ||||
| 	return security.NewJWT( | ||||
| 		jwt.MapClaims{ | ||||
| 			"id":           record.Id, | ||||
| 			"type":         TypeAuthRecord, | ||||
| @@ -32,7 +32,7 @@ func NewRecordVerifyToken(app core.App, record *models.Record) (string, error) { | ||||
| 		return "", errors.New("The record is not from an auth collection.") | ||||
| 	} | ||||
|  | ||||
| 	return security.NewToken( | ||||
| 	return security.NewJWT( | ||||
| 		jwt.MapClaims{ | ||||
| 			"id":           record.Id, | ||||
| 			"type":         TypeAuthRecord, | ||||
| @@ -50,7 +50,7 @@ func NewRecordResetPasswordToken(app core.App, record *models.Record) (string, e | ||||
| 		return "", errors.New("The record is not from an auth collection.") | ||||
| 	} | ||||
|  | ||||
| 	return security.NewToken( | ||||
| 	return security.NewJWT( | ||||
| 		jwt.MapClaims{ | ||||
| 			"id":           record.Id, | ||||
| 			"type":         TypeAuthRecord, | ||||
| @@ -64,7 +64,7 @@ func NewRecordResetPasswordToken(app core.App, record *models.Record) (string, e | ||||
|  | ||||
| // NewRecordChangeEmailToken generates and returns a new auth record change email request token. | ||||
| func NewRecordChangeEmailToken(app core.App, record *models.Record, newEmail string) (string, error) { | ||||
| 	return security.NewToken( | ||||
| 	return security.NewJWT( | ||||
| 		jwt.MapClaims{ | ||||
| 			"id":           record.Id, | ||||
| 			"type":         TypeAuthRecord, | ||||
| @@ -83,7 +83,7 @@ func NewRecordFileToken(app core.App, record *models.Record) (string, error) { | ||||
| 		return "", errors.New("The record is not from an auth collection.") | ||||
| 	} | ||||
|  | ||||
| 	return security.NewToken( | ||||
| 	return security.NewJWT( | ||||
| 		jwt.MapClaims{ | ||||
| 			"id":           record.Id, | ||||
| 			"type":         TypeAuthRecord, | ||||
|   | ||||
| @@ -42,8 +42,8 @@ func ParseJWT(token string, verificationKey string) (jwt.MapClaims, error) { | ||||
| 	return nil, errors.New("Unable to parse token.") | ||||
| } | ||||
|  | ||||
| // NewToken generates and returns new HS256 signed JWT token. | ||||
| func NewToken(payload jwt.MapClaims, signingKey string, secondsDuration int64) (string, error) { | ||||
| // NewJWT generates and returns new HS256 signed JWT token. | ||||
| func NewJWT(payload jwt.MapClaims, signingKey string, secondsDuration int64) (string, error) { | ||||
| 	seconds := time.Duration(secondsDuration) * time.Second | ||||
|  | ||||
| 	claims := jwt.MapClaims{ | ||||
| @@ -56,3 +56,11 @@ func NewToken(payload jwt.MapClaims, signingKey string, secondsDuration int64) ( | ||||
|  | ||||
| 	return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(signingKey)) | ||||
| } | ||||
|  | ||||
| // Deprecated: | ||||
| // Consider replacing with NewJWT(). | ||||
| // | ||||
| // NewToken is a legacy alias for NewJWT that generates a HS256 signed JWT token. | ||||
| func NewToken(payload jwt.MapClaims, signingKey string, secondsDuration int64) (string, error) { | ||||
| 	return NewJWT(payload, signingKey, secondsDuration) | ||||
| } | ||||
|   | ||||
| @@ -125,7 +125,7 @@ func TestParseJWT(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestNewToken(t *testing.T) { | ||||
| func TestNewJWT(t *testing.T) { | ||||
| 	scenarios := []struct { | ||||
| 		claims      jwt.MapClaims | ||||
| 		key         string | ||||
| @@ -141,9 +141,9 @@ func TestNewToken(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	for i, scenario := range scenarios { | ||||
| 		token, tokenErr := security.NewToken(scenario.claims, scenario.key, scenario.duration) | ||||
| 		token, tokenErr := security.NewJWT(scenario.claims, scenario.key, scenario.duration) | ||||
| 		if tokenErr != nil { | ||||
| 			t.Errorf("(%d) Expected NewToken to succeed, got error %v", i, tokenErr) | ||||
| 			t.Errorf("(%d) Expected NewJWT to succeed, got error %v", i, tokenErr) | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user