@adonis-agora/authkit-testing 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +93 -0
- package/build/index.d.ts +7 -0
- package/build/index.js +4 -0
- package/build/src/account_store.d.ts +30 -0
- package/build/src/account_store.js +47 -0
- package/build/src/authenticator.d.ts +27 -0
- package/build/src/authenticator.js +39 -0
- package/build/src/identity.d.ts +6 -0
- package/build/src/identity.js +18 -0
- package/build/src/jwt.d.ts +62 -0
- package/build/src/jwt.js +73 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Davi de Carvalho
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @adonis-agora/authkit-testing
|
|
2
|
+
|
|
3
|
+
Test helpers so **host apps** built on AuthKit can test auth flows without booting
|
|
4
|
+
an IdP. Mint real signed ID tokens validated by a local JWKS, fake the
|
|
5
|
+
`ctx.auth` authenticator, fake an account store, and build valid identities.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add -D @adonis-agora/authkit-testing
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
### `createTestIdentity(overrides?)`
|
|
14
|
+
|
|
15
|
+
Returns a valid `Identity` with sane defaults.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createTestIdentity } from '@adonis-agora/authkit-testing'
|
|
19
|
+
|
|
20
|
+
const identity = createTestIdentity({ globalRoles: ['ADMIN'] })
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### `mintTestIdToken({ issuer, clientId, claims?, key?, expiresInSeconds? })`
|
|
24
|
+
|
|
25
|
+
Mints a **real** RS256-signed JWT plus the public JWKS that validates it. Returns
|
|
26
|
+
`{ token, key, jwks }`.
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { mintTestIdToken, serveJwks } from '@adonis-agora/authkit-testing'
|
|
30
|
+
import { resolvers } from '@adonis-agora/authkit-client'
|
|
31
|
+
|
|
32
|
+
const { token, jwks } = await mintTestIdToken({
|
|
33
|
+
issuer: 'https://idp.test',
|
|
34
|
+
clientId: 'my-app',
|
|
35
|
+
claims: { sub: 'user-42', email: 'jane@test.dev', roles: ['ADMIN'] },
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Serve the JWKS in-process so resolvers.jwt({ jwksUri }) can validate it.
|
|
39
|
+
const served = await serveJwks(jwks)
|
|
40
|
+
const factory = resolvers.jwt({ jwksUri: served.jwksUri })
|
|
41
|
+
const resolver = await factory.resolver({
|
|
42
|
+
issuer: 'https://idp.test',
|
|
43
|
+
clientId: 'my-app',
|
|
44
|
+
sessionKey: 'authkit',
|
|
45
|
+
globalRolesClaim: 'roles',
|
|
46
|
+
})
|
|
47
|
+
// ... use resolver in your host test, then:
|
|
48
|
+
await served.close()
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The `JwtResolver` uses `createRemoteJWKSet`, so it needs a URL — `serveJwks()`
|
|
52
|
+
provides one locally with no external network. You can also reuse a single
|
|
53
|
+
keypair across tokens via `generateTestKeyPair()` + the `key` option, or get the
|
|
54
|
+
raw public JWKS object with `jwksFromKey(key)` / `testJwks(key)`.
|
|
55
|
+
|
|
56
|
+
### `fakeAuthenticator({ identity?, user?, appRoles? })`
|
|
57
|
+
|
|
58
|
+
Object satisfying the client's `Authenticator` surface, for injecting into
|
|
59
|
+
`ctx.auth` in controller tests.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { fakeAuthenticator } from '@adonis-agora/authkit-testing'
|
|
63
|
+
|
|
64
|
+
const auth = fakeAuthenticator({
|
|
65
|
+
identity: createTestIdentity({ globalRoles: ['ADMIN'] }),
|
|
66
|
+
appRoles: ['editor'],
|
|
67
|
+
})
|
|
68
|
+
auth.hasGlobalRole('ADMIN') // true
|
|
69
|
+
await auth.hasAppRole('editor') // true
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Pass `identity: null` to simulate an anonymous request.
|
|
73
|
+
|
|
74
|
+
### `fakeAccountStore(options?)`
|
|
75
|
+
|
|
76
|
+
A capability-aware fake `AccountStore`. The core methods are always present;
|
|
77
|
+
MFA, passkeys and account-security capabilities are opt-in so you can exercise
|
|
78
|
+
the server's `supportsMfa` / `supportsPasskeys` / `supportsAccountSecurity`
|
|
79
|
+
type guards.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { fakeAccountStore } from '@adonis-agora/authkit-testing'
|
|
83
|
+
|
|
84
|
+
const store = fakeAccountStore({
|
|
85
|
+
account: { id: 'u1', email: 'a@b.com', globalRoles: ['ADMIN'] },
|
|
86
|
+
withMfa: true,
|
|
87
|
+
overrides: { findById: async () => null },
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createTestIdentity } from './src/identity.js';
|
|
2
|
+
export { mintTestIdToken, generateTestKeyPair, testJwks, jwksFromKey, serveJwks, } from './src/jwt.js';
|
|
3
|
+
export type { TestKeyPair, MintTestIdTokenOptions, MintedToken, ServedJwks, } from './src/jwt.js';
|
|
4
|
+
export { fakeAuthenticator } from './src/authenticator.js';
|
|
5
|
+
export type { FakeAuthenticatorLike, FakeAuthenticatorOptions } from './src/authenticator.js';
|
|
6
|
+
export { fakeAccountStore } from './src/account_store.js';
|
|
7
|
+
export type { FakeAuthAccount, FakeAccountStoreOptions } from './src/account_store.js';
|
package/build/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createTestIdentity } from './src/identity.js';
|
|
2
|
+
export { mintTestIdToken, generateTestKeyPair, testJwks, jwksFromKey, serveJwks, } from './src/jwt.js';
|
|
3
|
+
export { fakeAuthenticator } from './src/authenticator.js';
|
|
4
|
+
export { fakeAccountStore } from './src/account_store.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DTO público da conta (espelha `AuthAccount` do server). Mantido local para o
|
|
3
|
+
* pacote de testing não depender do server em runtime (que arrasta Lucid etc.).
|
|
4
|
+
*/
|
|
5
|
+
export interface FakeAuthAccount {
|
|
6
|
+
id: string;
|
|
7
|
+
email: string;
|
|
8
|
+
globalRoles?: string[];
|
|
9
|
+
name?: string;
|
|
10
|
+
avatarUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface FakeAccountStoreOptions {
|
|
13
|
+
/** conta fixa retornada por findById/verifyCredentials/findByEmail. */
|
|
14
|
+
account?: FakeAuthAccount;
|
|
15
|
+
/** inclui métodos da capacidade MfaCapability (supportsMfa → true). */
|
|
16
|
+
withMfa?: boolean;
|
|
17
|
+
/** inclui métodos da capacidade WebauthnCapability (supportsPasskeys → true). */
|
|
18
|
+
withPasskeys?: boolean;
|
|
19
|
+
/** inclui métodos da capacidade AccountSecurityCapability (supportsAccountSecurity → true). */
|
|
20
|
+
withAccountSecurity?: boolean;
|
|
21
|
+
/** sobrescreve qualquer método individualmente. */
|
|
22
|
+
overrides?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* AccountStore mínimo para testes de host, capability-aware. O núcleo
|
|
26
|
+
* (findById/verifyCredentials/create/...) e a capacidade de provider-identity e
|
|
27
|
+
* admin sempre estão presentes; MFA, passkeys e account-security são opt-in via
|
|
28
|
+
* flags para exercitar os type guards `supports*` do server.
|
|
29
|
+
*/
|
|
30
|
+
export declare function fakeAccountStore(options?: FakeAccountStoreOptions): Record<string, unknown>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AccountStore mínimo para testes de host, capability-aware. O núcleo
|
|
3
|
+
* (findById/verifyCredentials/create/...) e a capacidade de provider-identity e
|
|
4
|
+
* admin sempre estão presentes; MFA, passkeys e account-security são opt-in via
|
|
5
|
+
* flags para exercitar os type guards `supports*` do server.
|
|
6
|
+
*/
|
|
7
|
+
export function fakeAccountStore(options = {}) {
|
|
8
|
+
const fixed = options.account ?? {
|
|
9
|
+
id: 'u1',
|
|
10
|
+
email: 'a@b.com',
|
|
11
|
+
globalRoles: ['ADMIN'],
|
|
12
|
+
};
|
|
13
|
+
const core = {
|
|
14
|
+
findById: async (id) => ({ ...fixed, id }),
|
|
15
|
+
verifyCredentials: async (email) => (email === fixed.email ? { ...fixed } : null),
|
|
16
|
+
findByEmail: async (email) => (email === fixed.email ? { ...fixed } : null),
|
|
17
|
+
create: async (input) => ({
|
|
18
|
+
id: 'new',
|
|
19
|
+
email: input.email,
|
|
20
|
+
globalRoles: input.globalRoles ?? [],
|
|
21
|
+
}),
|
|
22
|
+
findByProviderIdentity: async () => null,
|
|
23
|
+
linkProviderIdentity: async () => { },
|
|
24
|
+
issuePasswordResetToken: async () => null,
|
|
25
|
+
consumePasswordResetToken: async () => false,
|
|
26
|
+
issueEmailVerificationToken: async () => null,
|
|
27
|
+
consumeEmailVerificationToken: async () => false,
|
|
28
|
+
listAccounts: async () => ({ data: [{ ...fixed }], total: 1 }),
|
|
29
|
+
setGlobalRoles: async () => { },
|
|
30
|
+
};
|
|
31
|
+
if (options.withMfa) {
|
|
32
|
+
core.getMfaState = async () => ({ enabled: false });
|
|
33
|
+
core.enableMfa = async () => { };
|
|
34
|
+
core.disableMfa = async () => { };
|
|
35
|
+
}
|
|
36
|
+
if (options.withPasskeys) {
|
|
37
|
+
core.listPasskeys = async () => [];
|
|
38
|
+
core.registerPasskey = async () => { };
|
|
39
|
+
core.deletePasskey = async () => { };
|
|
40
|
+
}
|
|
41
|
+
if (options.withAccountSecurity) {
|
|
42
|
+
core.changePassword = async () => true;
|
|
43
|
+
core.requestEmailChange = async () => null;
|
|
44
|
+
core.confirmEmailChange = async () => ({ ok: false });
|
|
45
|
+
}
|
|
46
|
+
return { ...core, ...(options.overrides ?? {}) };
|
|
47
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Identity } from '@adonis-agora/authkit-core';
|
|
2
|
+
/**
|
|
3
|
+
* Superfície pública do `Authenticator` do client (o que `ctx.auth` expõe).
|
|
4
|
+
* Reproduzida aqui para não acoplar o pacote de testing ao client em runtime.
|
|
5
|
+
*/
|
|
6
|
+
export interface FakeAuthenticatorLike {
|
|
7
|
+
getIdentity(): Promise<Identity | null>;
|
|
8
|
+
readonly identity: Identity | null;
|
|
9
|
+
authenticate(): Promise<Identity>;
|
|
10
|
+
check(): Promise<boolean>;
|
|
11
|
+
hasGlobalRole(role: string): boolean;
|
|
12
|
+
hasAppRole(role: string): Promise<boolean>;
|
|
13
|
+
getUser(): Promise<unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface FakeAuthenticatorOptions {
|
|
16
|
+
/** identidade resolvida; default = {@link createTestIdentity}. Passe `null` para simular anônimo. */
|
|
17
|
+
identity?: Identity | null;
|
|
18
|
+
/** usuário de domínio retornado por `getUser()`. */
|
|
19
|
+
user?: unknown;
|
|
20
|
+
/** roles de app reconhecidas por `hasAppRole`. */
|
|
21
|
+
appRoles?: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Cria um objeto que satisfaz a superfície do `Authenticator` do client para
|
|
25
|
+
* injetar em `ctx.auth` em testes de controller — sem resolver tokens de verdade.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fakeAuthenticator(options?: FakeAuthenticatorOptions): FakeAuthenticatorLike;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createTestIdentity } from './identity.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cria um objeto que satisfaz a superfície do `Authenticator` do client para
|
|
4
|
+
* injetar em `ctx.auth` em testes de controller — sem resolver tokens de verdade.
|
|
5
|
+
*/
|
|
6
|
+
export function fakeAuthenticator(options = {}) {
|
|
7
|
+
const identity = options.identity === undefined ? createTestIdentity() : options.identity;
|
|
8
|
+
const appRoles = options.appRoles ?? [];
|
|
9
|
+
const user = 'user' in options ? options.user : identity;
|
|
10
|
+
return {
|
|
11
|
+
async getIdentity() {
|
|
12
|
+
return identity;
|
|
13
|
+
},
|
|
14
|
+
get identity() {
|
|
15
|
+
return identity;
|
|
16
|
+
},
|
|
17
|
+
async authenticate() {
|
|
18
|
+
if (!identity)
|
|
19
|
+
throw new Error('Não autenticado');
|
|
20
|
+
return identity;
|
|
21
|
+
},
|
|
22
|
+
async check() {
|
|
23
|
+
return identity !== null;
|
|
24
|
+
},
|
|
25
|
+
hasGlobalRole(role) {
|
|
26
|
+
return identity?.globalRoles.includes(role) ?? false;
|
|
27
|
+
},
|
|
28
|
+
async hasAppRole(role) {
|
|
29
|
+
if (!identity)
|
|
30
|
+
return false;
|
|
31
|
+
return appRoles.includes(role);
|
|
32
|
+
},
|
|
33
|
+
async getUser() {
|
|
34
|
+
if (!identity)
|
|
35
|
+
return null;
|
|
36
|
+
return user;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Identity } from '@adonis-agora/authkit-core';
|
|
2
|
+
/**
|
|
3
|
+
* Cria uma {@link Identity} válida com valores padrão sãos para usar em testes.
|
|
4
|
+
* Sobrescreva qualquer campo via `overrides`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createTestIdentity(overrides?: Partial<Identity>): Identity;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cria uma {@link Identity} válida com valores padrão sãos para usar em testes.
|
|
3
|
+
* Sobrescreva qualquer campo via `overrides`.
|
|
4
|
+
*/
|
|
5
|
+
export function createTestIdentity(overrides = {}) {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
return {
|
|
8
|
+
userId: 'test-user-id',
|
|
9
|
+
email: 'test@example.com',
|
|
10
|
+
globalRoles: [],
|
|
11
|
+
profile: { name: 'Test User', avatarUrl: undefined },
|
|
12
|
+
sessionId: 'test-session-id',
|
|
13
|
+
issuedAt: now,
|
|
14
|
+
expiresAt: now + 60 * 60 * 1000,
|
|
15
|
+
raw: {},
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Server } from 'node:http';
|
|
2
|
+
import { type JWK, type KeyLike } from 'jose';
|
|
3
|
+
export interface TestKeyPair {
|
|
4
|
+
privateKey: KeyLike;
|
|
5
|
+
publicKey: KeyLike;
|
|
6
|
+
kid: string;
|
|
7
|
+
}
|
|
8
|
+
/** Gera um par de chaves RS256 extraível com um `kid` estável, para minting de tokens em testes. */
|
|
9
|
+
export declare function generateTestKeyPair(kid?: string): Promise<TestKeyPair>;
|
|
10
|
+
export interface MintTestIdTokenOptions {
|
|
11
|
+
/** claim `iss` — deve casar com o `issuer` configurado no resolver. */
|
|
12
|
+
issuer: string;
|
|
13
|
+
/** claim `aud` — deve casar com o `audience`/clientId configurado no resolver. */
|
|
14
|
+
clientId: string;
|
|
15
|
+
/** claims extras / overrides (ex.: `sub`, `email`, `roles`). */
|
|
16
|
+
claims?: Record<string, unknown>;
|
|
17
|
+
/** par de chaves para assinar; um novo é gerado se omitido. */
|
|
18
|
+
key?: TestKeyPair;
|
|
19
|
+
/** validade em segundos (default 3600). */
|
|
20
|
+
expiresInSeconds?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface MintedToken {
|
|
23
|
+
/** o JWT assinado (RS256) real. */
|
|
24
|
+
token: string;
|
|
25
|
+
/** par de chaves usado (gerado ou fornecido). */
|
|
26
|
+
key: TestKeyPair;
|
|
27
|
+
/** JWKS público (apenas a chave pública) para validação local. */
|
|
28
|
+
jwks: {
|
|
29
|
+
keys: JWK[];
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Emite um ID token JWT REAL assinado com RS256 (não um mock). O JWKS público
|
|
34
|
+
* resultante valida o token através do `JwtResolver` quando servido em
|
|
35
|
+
* {@link serveJwks} e apontado por `resolvers.jwt({ jwksUri })`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function mintTestIdToken(options: MintTestIdTokenOptions): Promise<MintedToken>;
|
|
38
|
+
/** Exporta o JWKS público (uma chave) de um par de chaves de teste. */
|
|
39
|
+
export declare function testJwks(key: TestKeyPair): Promise<{
|
|
40
|
+
keys: JWK[];
|
|
41
|
+
}>;
|
|
42
|
+
/** Objeto pronto para alimentar um `createLocalJWKSet` — o JWKS público como `{ keys }`. */
|
|
43
|
+
export declare function jwksFromKey(key: TestKeyPair): Promise<{
|
|
44
|
+
keys: JWK[];
|
|
45
|
+
}>;
|
|
46
|
+
export interface ServedJwks {
|
|
47
|
+
/** URL do endpoint JWKS in-process (passe ao `resolvers.jwt({ jwksUri })`). */
|
|
48
|
+
jwksUri: string;
|
|
49
|
+
/** servidor http subjacente. */
|
|
50
|
+
server: Server;
|
|
51
|
+
/** fecha o servidor (chame no `group.teardown`). */
|
|
52
|
+
close: () => Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Sobe um servidor http in-process que serve o JWKS público em
|
|
56
|
+
* `GET /.well-known/jwks.json`. O `JwtResolver` usa `createRemoteJWKSet`, então
|
|
57
|
+
* ele só aceita uma URL — este helper fornece essa URL localmente sem rede
|
|
58
|
+
* externa nem IdP.
|
|
59
|
+
*/
|
|
60
|
+
export declare function serveJwks(jwks: {
|
|
61
|
+
keys: JWK[];
|
|
62
|
+
}): Promise<ServedJwks>;
|
package/build/src/jwt.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { SignJWT, exportJWK, generateKeyPair, } from 'jose';
|
|
4
|
+
const ALG = 'RS256';
|
|
5
|
+
/** Gera um par de chaves RS256 extraível com um `kid` estável, para minting de tokens em testes. */
|
|
6
|
+
export async function generateTestKeyPair(kid = randomUUID()) {
|
|
7
|
+
const { privateKey, publicKey } = await generateKeyPair(ALG, { extractable: true });
|
|
8
|
+
return { privateKey, publicKey, kid };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Emite um ID token JWT REAL assinado com RS256 (não um mock). O JWKS público
|
|
12
|
+
* resultante valida o token através do `JwtResolver` quando servido em
|
|
13
|
+
* {@link serveJwks} e apontado por `resolvers.jwt({ jwksUri })`.
|
|
14
|
+
*/
|
|
15
|
+
export async function mintTestIdToken(options) {
|
|
16
|
+
const key = options.key ?? (await generateTestKeyPair());
|
|
17
|
+
const expiresIn = options.expiresInSeconds ?? 3600;
|
|
18
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
19
|
+
const baseClaims = {
|
|
20
|
+
sub: 'test-user-id',
|
|
21
|
+
email: 'test@example.com',
|
|
22
|
+
...options.claims,
|
|
23
|
+
};
|
|
24
|
+
const token = await new SignJWT(baseClaims)
|
|
25
|
+
.setProtectedHeader({ alg: ALG, kid: key.kid })
|
|
26
|
+
.setIssuer(options.issuer)
|
|
27
|
+
.setAudience(options.clientId)
|
|
28
|
+
.setIssuedAt(nowSeconds)
|
|
29
|
+
.setExpirationTime(nowSeconds + expiresIn)
|
|
30
|
+
.sign(key.privateKey);
|
|
31
|
+
return { token, key, jwks: await testJwks(key) };
|
|
32
|
+
}
|
|
33
|
+
/** Exporta o JWKS público (uma chave) de um par de chaves de teste. */
|
|
34
|
+
export async function testJwks(key) {
|
|
35
|
+
const jwk = await exportJWK(key.publicKey);
|
|
36
|
+
jwk.use = 'sig';
|
|
37
|
+
jwk.alg = ALG;
|
|
38
|
+
jwk.kid = key.kid;
|
|
39
|
+
return { keys: [jwk] };
|
|
40
|
+
}
|
|
41
|
+
/** Objeto pronto para alimentar um `createLocalJWKSet` — o JWKS público como `{ keys }`. */
|
|
42
|
+
export async function jwksFromKey(key) {
|
|
43
|
+
return testJwks(key);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Sobe um servidor http in-process que serve o JWKS público em
|
|
47
|
+
* `GET /.well-known/jwks.json`. O `JwtResolver` usa `createRemoteJWKSet`, então
|
|
48
|
+
* ele só aceita uma URL — este helper fornece essa URL localmente sem rede
|
|
49
|
+
* externa nem IdP.
|
|
50
|
+
*/
|
|
51
|
+
export async function serveJwks(jwks) {
|
|
52
|
+
const body = JSON.stringify(jwks);
|
|
53
|
+
const server = createServer((req, res) => {
|
|
54
|
+
if (req.url?.startsWith('/.well-known/jwks.json')) {
|
|
55
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
56
|
+
res.end(body);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
res.writeHead(404);
|
|
60
|
+
res.end();
|
|
61
|
+
});
|
|
62
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
63
|
+
const address = server.address();
|
|
64
|
+
if (!address || typeof address === 'string') {
|
|
65
|
+
throw new Error('Falha ao obter a porta do servidor JWKS de teste');
|
|
66
|
+
}
|
|
67
|
+
const jwksUri = `http://127.0.0.1:${address.port}/.well-known/jwks.json`;
|
|
68
|
+
return {
|
|
69
|
+
jwksUri,
|
|
70
|
+
server,
|
|
71
|
+
close: () => new Promise((resolve) => server.close(() => resolve())),
|
|
72
|
+
};
|
|
73
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adonis-agora/authkit-testing",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Test helpers for apps built on AuthKit: mint real signed ID tokens with a local JWKS, fake authenticators and account stores, and build valid identities — so host apps can test auth flows without booting an IdP.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "dudousxd",
|
|
7
|
+
"homepage": "https://github.com/DavideCarvalho/adonis-authkit/tree/main/packages/authkit-testing#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/DavideCarvalho/adonis-authkit.git",
|
|
11
|
+
"directory": "packages/authkit-testing"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/DavideCarvalho/adonis-authkit/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"adonisjs",
|
|
18
|
+
"authentication",
|
|
19
|
+
"authkit",
|
|
20
|
+
"testing",
|
|
21
|
+
"jwt",
|
|
22
|
+
"jwks",
|
|
23
|
+
"oidc"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./build/index.js",
|
|
30
|
+
"types": "./build/index.d.ts",
|
|
31
|
+
"files": [
|
|
32
|
+
"build"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
".": "./build/index.js",
|
|
36
|
+
"./types": "./build/index.js"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"jose": "^5.9.0",
|
|
40
|
+
"@adonis-agora/authkit-core": "0.7.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@adonisjs/tsconfig": "2.0.0",
|
|
44
|
+
"@japa/assert": "4.2.0",
|
|
45
|
+
"@japa/runner": "5.3.0",
|
|
46
|
+
"@poppinss/ts-exec": "1.4.4",
|
|
47
|
+
"@types/node": "25.9.1",
|
|
48
|
+
"typescript": "6.0.3",
|
|
49
|
+
"@adonis-agora/authkit-client": "0.9.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsc",
|
|
53
|
+
"typecheck": "tsc --noEmit",
|
|
54
|
+
"test": "node --import=@poppinss/ts-exec bin/test.ts"
|
|
55
|
+
}
|
|
56
|
+
}
|