@ambushsoftworks/nestjs-auth-graphql 0.16.0-rc.2 → 0.16.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/CHANGELOG.md +93 -0
- package/README.md +17 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/passkey-repository.contract.d.ts +13 -2
- package/dist/testing/passkey-repository.contract.d.ts.map +1 -1
- package/dist/testing/passkey-repository.contract.js +175 -67
- package/dist/testing/passkey-repository.contract.js.map +1 -1
- package/examples/passkeys/prisma-passkey.repository.ts +166 -0
- package/examples/passkeys/schema.prisma +46 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -27,6 +27,99 @@ for buried obligations. **The marker was introduced in 0.9.0 and has not been
|
|
|
27
27
|
retro-applied**, so it is reliable from 0.9.0 onward only. Convention adopted
|
|
28
28
|
from `@ambushsoftworks/nestjs-payments-graphql`.
|
|
29
29
|
|
|
30
|
+
## [0.16.0] - 2026-10-01
|
|
31
|
+
|
|
32
|
+
Passkeys (WebAuthn), and step-up re-authentication to protect adding one.
|
|
33
|
+
Identical to `0.16.0-rc.3`, which LiftIQ piloted on a real Android Credential
|
|
34
|
+
Manager and against Postgres. This entry is the summary for anyone coming from
|
|
35
|
+
0.15.x; the three release-candidate entries below have the detail.
|
|
36
|
+
|
|
37
|
+
**Nothing changes unless you enable passkeys**, with two exceptions noted
|
|
38
|
+
under Changed.
|
|
39
|
+
|
|
40
|
+
### ⚠ External configuration required
|
|
41
|
+
|
|
42
|
+
Only if you enable passkeys (details: README, "Passkeys → Platform setup"):
|
|
43
|
+
|
|
44
|
+
- Install the optional peer `@simplewebauthn/server@^14`; it needs Node.js 20+.
|
|
45
|
+
- **Web:** the frontend on HTTPS at `passkey.rp.id` or a subdomain, its exact
|
|
46
|
+
origin listed in `passkey.rp.origins`.
|
|
47
|
+
- **Android:** `https://<rp.id>/.well-known/assetlinks.json` granting
|
|
48
|
+
`delegate_permission/common.get_login_creds`, and
|
|
49
|
+
`android:apk-key-hash:<base64url SHA-256 of the signing certificate>` in
|
|
50
|
+
`passkey.rp.origins`.
|
|
51
|
+
- **iOS:** `https://<rp.id>/.well-known/apple-app-site-association` with
|
|
52
|
+
`webcredentials.apps`, and `webcredentials:<rp.id>` in Associated Domains.
|
|
53
|
+
- **Database:** passkey credential and challenge tables
|
|
54
|
+
(`examples/passkeys/schema.prisma`), and the value `step-up` if your
|
|
55
|
+
verification repository stores its type in a database enum.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- **Passkeys**: `PasskeyService`, `IPasskeyRepository`, 11 `perform*` methods
|
|
60
|
+
on `BaseAuthResolver`. Usernameless sign-in, browser autofill, user
|
|
61
|
+
verification required by default (configurable), realm-bound credentials.
|
|
62
|
+
- **Step-up re-authentication** (`StepUpService`): password, passkey or email
|
|
63
|
+
code, giving a five-minute token bound to user and realm and reusable until
|
|
64
|
+
it expires. **Required to add a passkey.**
|
|
65
|
+
- **Owner notification emails** when a passkey is added or removed, sent by
|
|
66
|
+
the package through optional `IEmailService` methods; turn off with
|
|
67
|
+
`passkey.emailNotifications`.
|
|
68
|
+
- **`@ambushsoftworks/nestjs-auth-graphql/testing`**:
|
|
69
|
+
`assertPasskeyRepositoryContract` (with `userIds`, `realms` and `cleanup`
|
|
70
|
+
for real databases) and `SoftwareAuthenticator`.
|
|
71
|
+
- **`examples/passkeys/`**: reference Prisma schema and adapter, verified with
|
|
72
|
+
the contract on PostgreSQL 16.
|
|
73
|
+
- Error codes `STEP_UP_FAILED`, `STEP_UP_REQUIRED`, `STEP_UP_UNAVAILABLE`,
|
|
74
|
+
`PASSKEY_AUTHENTICATION_FAILED`, `PASSKEY_ALREADY_REGISTERED`,
|
|
75
|
+
`CANNOT_REMOVE_LAST_AUTH_METHOD`. **Do not map `STEP_UP_FAILED` to a token
|
|
76
|
+
refresh**; it is not an expired session.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- `RepositoryVerificationType` includes `STEP_UP`. An adapter with an
|
|
81
|
+
exhaustive `switch` over it needs the new case.
|
|
82
|
+
- `BaseAuthResolver` gains protected members `authPasskeys` and `authStepUp`
|
|
83
|
+
(its constructor is unchanged). A subclass declaring members with those
|
|
84
|
+
names must rename them.
|
|
85
|
+
|
|
86
|
+
### Upgrading from an RC
|
|
87
|
+
|
|
88
|
+
From rc.1, read the rc.2 entry: the error codes changed, and the package now
|
|
89
|
+
sends the passkey emails, so remove any you send from the hooks. From rc.2,
|
|
90
|
+
nothing breaks; the contract gained options. From rc.3, nothing changes.
|
|
91
|
+
|
|
92
|
+
## [0.16.0-rc.3] - 2026-10-01
|
|
93
|
+
|
|
94
|
+
The repository contract from rc.2 could not run against a real schema,
|
|
95
|
+
including the reference schema in this README. Found by LiftIQ on Postgres.
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
|
|
99
|
+
- **`assertPasskeyRepositoryContract` takes options** (defaults unchanged):
|
|
100
|
+
- `userIds: [a, b, ...]`: existing users to attach credentials to. Without
|
|
101
|
+
them the contract invented user ids, so any schema with a foreign key to
|
|
102
|
+
users, the README's included, failed with `P2003` on the first insert.
|
|
103
|
+
- `realms: false`: for deployments without realms. Every fixture carried a
|
|
104
|
+
realm and every check demanded it back, though the interface says realm
|
|
105
|
+
is only set when realms are enabled.
|
|
106
|
+
- `cleanup({ credentialIds, challengeIds })`: called once, pass or fail,
|
|
107
|
+
with every id created. Before, every run left rows in the database the
|
|
108
|
+
README told adopters to use.
|
|
109
|
+
- **Listing is checked by containment, not an exact list**, so users who
|
|
110
|
+
already have passkeys do not fail it.
|
|
111
|
+
- **`userIds` without `cleanup` is refused**: the contract creates active
|
|
112
|
+
passkeys on those users, which would show in their passkey lists. Pass
|
|
113
|
+
`cleanup: () => {}` to keep them on purpose.
|
|
114
|
+
|
|
115
|
+
### Added
|
|
116
|
+
|
|
117
|
+
- **`examples/passkeys/`**: the reference Prisma schema and a
|
|
118
|
+
`PrismaPasskeyRepository` built from the README's snippets, verified with the
|
|
119
|
+
contract on PostgreSQL 16, with and without realms, with a foreign key to
|
|
120
|
+
users, and with cleanup removing every row on both a passing and a failing
|
|
121
|
+
run.
|
|
122
|
+
|
|
30
123
|
## [0.16.0-rc.2] - 2026-10-01
|
|
31
124
|
|
|
32
125
|
Fixes from LiftIQ's pilot of rc.1 on a real Android Credential Manager. Still a
|
package/README.md
CHANGED
|
@@ -1276,7 +1276,14 @@ tests use, against a real database:
|
|
|
1276
1276
|
import { assertPasskeyRepositoryContract } from '@ambushsoftworks/nestjs-auth-graphql/testing';
|
|
1277
1277
|
|
|
1278
1278
|
it('meets the passkey repository contract', async () => {
|
|
1279
|
-
await assertPasskeyRepositoryContract(() => new PrismaPasskeyRepository(prisma)
|
|
1279
|
+
await assertPasskeyRepositoryContract(() => new PrismaPasskeyRepository(prisma), {
|
|
1280
|
+
userIds: [alice.id, bob.id], // existing users: the reference schema has a foreign key to User
|
|
1281
|
+
realms: false, // only if you do not use realms
|
|
1282
|
+
cleanup: async ({ credentialIds, challengeIds }) => {
|
|
1283
|
+
await prisma.passkeyCredential.deleteMany({ where: { credentialId: { in: credentialIds } } });
|
|
1284
|
+
await prisma.passkeyChallenge.deleteMany({ where: { id: { in: challengeIds } } });
|
|
1285
|
+
},
|
|
1286
|
+
});
|
|
1280
1287
|
});
|
|
1281
1288
|
```
|
|
1282
1289
|
|
|
@@ -1284,6 +1291,15 @@ It resolves when the adapter is correct and otherwise rejects naming the rule
|
|
|
1284
1291
|
broken: concurrent claims of one challenge, a counter column too small for a
|
|
1285
1292
|
uint32, an unmapped duplicate, a dropped realm, hidden deactivated
|
|
1286
1293
|
credentials, expiry left to a TTL. Framework-agnostic.
|
|
1294
|
+
|
|
1295
|
+
| Option | Default | Use it when |
|
|
1296
|
+
|---|---|---|
|
|
1297
|
+
| `userIds` | invented ids | Your credentials table has a foreign key to users, as the reference schema does. At least two existing users; they may already have passkeys. **Requires `cleanup`** |
|
|
1298
|
+
| `realms` | `true` | `false` when you do not set `realmExtractorInstance` and store no realm |
|
|
1299
|
+
| `cleanup` | none | Always, against a shared database. Called once, pass or fail, with every id created; the list may include ids never stored, so delete with `deleteMany` |
|
|
1300
|
+
|
|
1301
|
+
`examples/passkeys/` has the reference schema and a Prisma adapter that pass
|
|
1302
|
+
this contract on PostgreSQL 16, with and without realms.
|
|
1287
1303
|
`InMemoryPasskeyRepository` is exported for tests and single-instance
|
|
1288
1304
|
development.
|
|
1289
1305
|
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { assertPasskeyRepositoryContract, PasskeyRepositoryContractError, } from './passkey-repository.contract';
|
|
1
|
+
export { assertPasskeyRepositoryContract, PasskeyRepositoryContractError, type PasskeyRepositoryContractOptions, type PasskeyContractCreatedIds, } from './passkey-repository.contract';
|
|
2
2
|
export { SoftwareAuthenticator, type CreateOptions as SoftwareAuthenticatorCreateOptions, type GetOptions as SoftwareAuthenticatorGetOptions, type SoftwareAlgorithm, } from '../test-utils/software-authenticator';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,qBAAqB,EACrB,KAAK,aAAa,IAAI,kCAAkC,EACxD,KAAK,UAAU,IAAI,+BAA+B,EAClD,KAAK,iBAAiB,GACvB,MAAM,sCAAsC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,EAC9B,KAAK,gCAAgC,EACrC,KAAK,yBAAyB,GAC/B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,qBAAqB,EACrB,KAAK,aAAa,IAAI,kCAAkC,EACxD,KAAK,UAAU,IAAI,+BAA+B,EAClD,KAAK,iBAAiB,GACvB,MAAM,sCAAsC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":";;;AAMA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":";;;AAMA,6EAKuC;AAJrC,8IAAA,+BAA+B,OAAA;AAC/B,6IAAA,8BAA8B,OAAA;AAIhC,+EAK8C;AAJ5C,+HAAA,qBAAqB,OAAA"}
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { IPasskeyRepository } from '../interfaces/passkey-repository.interface';
|
|
2
2
|
export declare class PasskeyRepositoryContractError extends Error {
|
|
3
|
-
constructor(check: string, detail: string
|
|
3
|
+
constructor(check: string, detail: string, options?: {
|
|
4
|
+
cause?: unknown;
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
export interface PasskeyContractCreatedIds {
|
|
8
|
+
credentialIds: string[];
|
|
9
|
+
challengeIds: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface PasskeyRepositoryContractOptions {
|
|
12
|
+
realms?: boolean;
|
|
13
|
+
userIds?: readonly string[];
|
|
14
|
+
cleanup?: (created: PasskeyContractCreatedIds) => void | Promise<void>;
|
|
4
15
|
}
|
|
5
16
|
type Factory = () => IPasskeyRepository | Promise<IPasskeyRepository>;
|
|
6
|
-
export declare function assertPasskeyRepositoryContract(factory: Factory): Promise<void>;
|
|
17
|
+
export declare function assertPasskeyRepositoryContract(factory: Factory, options?: PasskeyRepositoryContractOptions): Promise<void>;
|
|
7
18
|
export {};
|
|
8
19
|
//# sourceMappingURL=passkey-repository.contract.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-repository.contract.d.ts","sourceRoot":"","sources":["../../src/testing/passkey-repository.contract.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,
|
|
1
|
+
{"version":3,"file":"passkey-repository.contract.d.ts","sourceRoot":"","sources":["../../src/testing/passkey-repository.contract.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAInB,MAAM,4CAA4C,CAAC;AAGpD,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAOzE;AAGD,MAAM,WAAW,yBAAyB;IAKxC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,gCAAgC;IAQ/C,MAAM,CAAC,EAAE,OAAO,CAAC;IAYjB,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAW5B,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,KAAK,OAAO,GAAG,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;AA8CtE,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,gCAAqC,GAC7C,OAAO,CAAC,IAAI,CAAC,CAwCf"}
|
|
@@ -5,60 +5,145 @@ exports.assertPasskeyRepositoryContract = assertPasskeyRepositoryContract;
|
|
|
5
5
|
const crypto_1 = require("crypto");
|
|
6
6
|
const passkey_repository_interface_1 = require("../interfaces/passkey-repository.interface");
|
|
7
7
|
class PasskeyRepositoryContractError extends Error {
|
|
8
|
-
constructor(check, detail) {
|
|
8
|
+
constructor(check, detail, options) {
|
|
9
9
|
super(`IPasskeyRepository contract — ${check}: ${detail}`);
|
|
10
10
|
this.name = 'PasskeyRepositoryContractError';
|
|
11
|
+
if (options?.cause !== undefined) {
|
|
12
|
+
this.cause = options.cause;
|
|
13
|
+
}
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
exports.PasskeyRepositoryContractError = PasskeyRepositoryContractError;
|
|
14
17
|
const CONCURRENCY = 8;
|
|
15
18
|
const MAX_COUNTER = 2 ** 32 - 1;
|
|
16
|
-
async function assertPasskeyRepositoryContract(factory) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
async function assertPasskeyRepositoryContract(factory, options = {}) {
|
|
20
|
+
validateOptions(options);
|
|
21
|
+
const run = new ContractRun(options);
|
|
22
|
+
let failure;
|
|
23
|
+
try {
|
|
24
|
+
for (const [name, check] of CHECKS) {
|
|
25
|
+
try {
|
|
26
|
+
const repository = run.track(await factory());
|
|
27
|
+
await check(repository, run);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof PasskeyRepositoryContractError)
|
|
31
|
+
throw error;
|
|
32
|
+
throw new PasskeyRepositoryContractError(name, `threw ${error instanceof Error ? `${error.name}: ${error.message}` : String(error)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
failure = error;
|
|
38
|
+
}
|
|
39
|
+
if (options.cleanup) {
|
|
19
40
|
try {
|
|
20
|
-
await
|
|
41
|
+
await options.cleanup(run.created());
|
|
21
42
|
}
|
|
22
|
-
catch (
|
|
23
|
-
if (
|
|
24
|
-
throw
|
|
25
|
-
|
|
43
|
+
catch (cleanupError) {
|
|
44
|
+
if (failure instanceof PasskeyRepositoryContractError) {
|
|
45
|
+
throw new PasskeyRepositoryContractError('cleanup', `${failure.message} — and cleanup then threw: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`, { cause: cleanupError });
|
|
46
|
+
}
|
|
47
|
+
if (failure === undefined)
|
|
48
|
+
throw cleanupError;
|
|
26
49
|
}
|
|
27
50
|
}
|
|
51
|
+
if (failure !== undefined)
|
|
52
|
+
throw failure;
|
|
53
|
+
}
|
|
54
|
+
function validateOptions(options) {
|
|
55
|
+
const { realms, userIds, cleanup } = options;
|
|
56
|
+
if (realms !== undefined && typeof realms !== 'boolean') {
|
|
57
|
+
throw new TypeError(`assertPasskeyRepositoryContract: realms must be a boolean; received ${String(realms)}`);
|
|
58
|
+
}
|
|
59
|
+
if (cleanup !== undefined && typeof cleanup !== 'function') {
|
|
60
|
+
throw new TypeError('assertPasskeyRepositoryContract: cleanup must be a function');
|
|
61
|
+
}
|
|
62
|
+
if (userIds !== undefined) {
|
|
63
|
+
const ids = Array.isArray(userIds) ? userIds : [];
|
|
64
|
+
if (ids.length < 2 || ids.some((id) => typeof id !== 'string' || id === '') || new Set(ids).size !== ids.length) {
|
|
65
|
+
throw new TypeError('assertPasskeyRepositoryContract: userIds must hold at least two distinct, non-empty ids of ' +
|
|
66
|
+
`existing users; received ${JSON.stringify(userIds)}`);
|
|
67
|
+
}
|
|
68
|
+
if (!cleanup) {
|
|
69
|
+
throw new TypeError('assertPasskeyRepositoryContract: userIds needs cleanup. The contract creates active passkeys ' +
|
|
70
|
+
'on those users, which would appear in their passkey lists. Delete them in cleanup, or pass ' +
|
|
71
|
+
'`cleanup: () => {}` to keep them on purpose.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
class ContractRun {
|
|
76
|
+
constructor(options) {
|
|
77
|
+
this.credentialIds = new Set();
|
|
78
|
+
this.challengeIds = new Set();
|
|
79
|
+
this.realms = options.realms ?? true;
|
|
80
|
+
this.users = options.userIds;
|
|
81
|
+
}
|
|
82
|
+
user(which) {
|
|
83
|
+
return this.users?.[which] ?? `contract-user-${(0, crypto_1.randomUUID)()}`;
|
|
84
|
+
}
|
|
85
|
+
realm(realm) {
|
|
86
|
+
return this.realms ? { realm } : {};
|
|
87
|
+
}
|
|
88
|
+
created() {
|
|
89
|
+
return { credentialIds: [...this.credentialIds], challengeIds: [...this.challengeIds] };
|
|
90
|
+
}
|
|
91
|
+
track(repo) {
|
|
92
|
+
return new Proxy(repo, {
|
|
93
|
+
get: (target, property, receiver) => {
|
|
94
|
+
const value = Reflect.get(target, property, receiver);
|
|
95
|
+
if (property === 'storeChallenge') {
|
|
96
|
+
return async (input) => {
|
|
97
|
+
const id = await value.call(target, input);
|
|
98
|
+
if (typeof id === 'string')
|
|
99
|
+
this.challengeIds.add(id);
|
|
100
|
+
return id;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (property === 'createCredential') {
|
|
104
|
+
return async (record) => {
|
|
105
|
+
this.credentialIds.add(record.credentialId);
|
|
106
|
+
return value.call(target, record);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return typeof value === 'function' ? value.bind(target) : value;
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
credential(overrides = {}) {
|
|
114
|
+
return {
|
|
115
|
+
credentialId: `contract-${(0, crypto_1.randomUUID)()}`,
|
|
116
|
+
userId: this.user(0),
|
|
117
|
+
...this.realm('contract-realm'),
|
|
118
|
+
publicKey: 'pQECAyYgASFYIA',
|
|
119
|
+
counter: 0,
|
|
120
|
+
userHandle: `handle-${(0, crypto_1.randomUUID)()}`,
|
|
121
|
+
transports: ['internal', 'hybrid'],
|
|
122
|
+
deviceType: 'multiDevice',
|
|
123
|
+
backedUp: true,
|
|
124
|
+
aaguid: '00000000-0000-0000-0000-000000000000',
|
|
125
|
+
name: 'Contract passkey',
|
|
126
|
+
createdAt: new Date(Math.floor(Date.now() / 1000) * 1000),
|
|
127
|
+
lastUsedAt: null,
|
|
128
|
+
isActive: true,
|
|
129
|
+
...overrides,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
28
132
|
}
|
|
29
133
|
function fail(check, detail) {
|
|
30
134
|
throw new PasskeyRepositoryContractError(check, detail);
|
|
31
135
|
}
|
|
32
|
-
function credential(overrides = {}) {
|
|
33
|
-
return {
|
|
34
|
-
credentialId: `contract-${(0, crypto_1.randomUUID)()}`,
|
|
35
|
-
userId: `contract-user-${(0, crypto_1.randomUUID)()}`,
|
|
36
|
-
realm: 'contract-realm',
|
|
37
|
-
publicKey: 'pQECAyYgASFYIA',
|
|
38
|
-
counter: 0,
|
|
39
|
-
userHandle: `handle-${(0, crypto_1.randomUUID)()}`,
|
|
40
|
-
transports: ['internal', 'hybrid'],
|
|
41
|
-
deviceType: 'multiDevice',
|
|
42
|
-
backedUp: true,
|
|
43
|
-
aaguid: '00000000-0000-0000-0000-000000000000',
|
|
44
|
-
name: 'Contract passkey',
|
|
45
|
-
createdAt: new Date(Math.floor(Date.now() / 1000) * 1000),
|
|
46
|
-
lastUsedAt: null,
|
|
47
|
-
isActive: true,
|
|
48
|
-
...overrides,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
136
|
const sameTime = (a, b) => a === b || (a instanceof Date && b instanceof Date && Math.abs(a.getTime() - b.getTime()) < 1000);
|
|
52
137
|
const CHECKS = [
|
|
53
138
|
[
|
|
54
139
|
'challenges round-trip every field',
|
|
55
|
-
async (repo) => {
|
|
140
|
+
async (repo, run) => {
|
|
56
141
|
const check = 'challenges round-trip every field';
|
|
57
142
|
const input = {
|
|
58
143
|
challenge: `challenge-${(0, crypto_1.randomUUID)()}`,
|
|
59
144
|
purpose: 'registration',
|
|
60
|
-
userId:
|
|
61
|
-
realm
|
|
145
|
+
userId: run.user(0),
|
|
146
|
+
...run.realm('contract-realm'),
|
|
62
147
|
userHandle: `handle-${(0, crypto_1.randomUUID)()}`,
|
|
63
148
|
expiresAt: new Date(Date.now() + 60_000),
|
|
64
149
|
};
|
|
@@ -68,7 +153,8 @@ const CHECKS = [
|
|
|
68
153
|
const got = await repo.consumeChallenge(id);
|
|
69
154
|
if (!got)
|
|
70
155
|
fail(check, 'consumeChallenge returned null for a fresh challenge');
|
|
71
|
-
|
|
156
|
+
const keys = ['challenge', 'purpose', 'userId', 'userHandle', ...(run.realms ? ['realm'] : [])];
|
|
157
|
+
for (const key of keys) {
|
|
72
158
|
if (got[key] !== input[key]) {
|
|
73
159
|
fail(check, `${key} came back as ${JSON.stringify(got[key])}, stored ${JSON.stringify(input[key])}`);
|
|
74
160
|
}
|
|
@@ -81,11 +167,12 @@ const CHECKS = [
|
|
|
81
167
|
],
|
|
82
168
|
[
|
|
83
169
|
'a challenge is claimed exactly once, under concurrency',
|
|
84
|
-
async (repo) => {
|
|
170
|
+
async (repo, run) => {
|
|
85
171
|
const check = 'a challenge is claimed exactly once, under concurrency';
|
|
86
172
|
const id = await repo.storeChallenge({
|
|
87
173
|
challenge: 'c',
|
|
88
174
|
purpose: 'authentication',
|
|
175
|
+
...run.realm('contract-realm'),
|
|
89
176
|
expiresAt: new Date(Date.now() + 60_000),
|
|
90
177
|
});
|
|
91
178
|
const claims = await Promise.all(Array.from({ length: CONCURRENCY }, () => repo.consumeChallenge(id)));
|
|
@@ -100,11 +187,12 @@ const CHECKS = [
|
|
|
100
187
|
],
|
|
101
188
|
[
|
|
102
189
|
'expired and unknown challenges answer null',
|
|
103
|
-
async (repo) => {
|
|
190
|
+
async (repo, run) => {
|
|
104
191
|
const check = 'expired and unknown challenges answer null';
|
|
105
192
|
const id = await repo.storeChallenge({
|
|
106
193
|
challenge: 'c',
|
|
107
194
|
purpose: 'authentication',
|
|
195
|
+
...run.realm('contract-realm'),
|
|
108
196
|
expiresAt: new Date(Date.now() - 1_000),
|
|
109
197
|
});
|
|
110
198
|
if (await repo.consumeChallenge(id)) {
|
|
@@ -116,17 +204,19 @@ const CHECKS = [
|
|
|
116
204
|
],
|
|
117
205
|
[
|
|
118
206
|
'credentials round-trip every field, counter up to 2^32-1',
|
|
119
|
-
async (repo) => {
|
|
207
|
+
async (repo, run) => {
|
|
120
208
|
const check = 'credentials round-trip every field';
|
|
121
|
-
const record = credential({ counter: MAX_COUNTER, metadata: { device: 'contract' } });
|
|
209
|
+
const record = run.credential({ counter: MAX_COUNTER, metadata: { device: 'contract' } });
|
|
122
210
|
await repo.createCredential(record);
|
|
123
|
-
const got = await repo.getCredential(record.credentialId);
|
|
211
|
+
const got = await repo.getCredential(record.credentialId, record.realm);
|
|
124
212
|
if (!got)
|
|
125
213
|
fail(check, 'getCredential returned null for a credential just created');
|
|
126
|
-
|
|
127
|
-
'credentialId', 'userId', '
|
|
214
|
+
const keys = [
|
|
215
|
+
'credentialId', 'userId', 'publicKey', 'counter', 'userHandle',
|
|
128
216
|
'deviceType', 'backedUp', 'aaguid', 'name', 'isActive',
|
|
129
|
-
|
|
217
|
+
...(run.realms ? ['realm'] : []),
|
|
218
|
+
];
|
|
219
|
+
for (const key of keys) {
|
|
130
220
|
if (got[key] !== record[key]) {
|
|
131
221
|
fail(check, `${key} came back as ${JSON.stringify(got[key])}, stored ${JSON.stringify(record[key])}` +
|
|
132
222
|
(key === 'counter' ? ' (a uint32: store it in a bigint column)' : ''));
|
|
@@ -143,19 +233,19 @@ const CHECKS = [
|
|
|
143
233
|
],
|
|
144
234
|
[
|
|
145
235
|
'a duplicate credential id throws PasskeyCredentialExistsError and leaves the original',
|
|
146
|
-
async (repo) => {
|
|
236
|
+
async (repo, run) => {
|
|
147
237
|
const check = 'a duplicate credential id is refused';
|
|
148
|
-
const original = credential();
|
|
238
|
+
const original = run.credential();
|
|
149
239
|
await repo.createCredential(original);
|
|
150
240
|
try {
|
|
151
|
-
await repo.createCredential(credential({ credentialId: original.credentialId }));
|
|
241
|
+
await repo.createCredential(run.credential({ credentialId: original.credentialId, userId: run.user(1) }));
|
|
152
242
|
}
|
|
153
243
|
catch (error) {
|
|
154
244
|
if (!(error instanceof passkey_repository_interface_1.PasskeyCredentialExistsError)) {
|
|
155
245
|
fail(check, `threw ${error instanceof Error ? error.name : String(error)}; map your unique-constraint ` +
|
|
156
246
|
'violation (Prisma P2002) to PasskeyCredentialExistsError');
|
|
157
247
|
}
|
|
158
|
-
const kept = await repo.getCredential(original.credentialId);
|
|
248
|
+
const kept = await repo.getCredential(original.credentialId, original.realm);
|
|
159
249
|
if (kept?.userId !== original.userId)
|
|
160
250
|
fail(check, "the original credential's owner changed");
|
|
161
251
|
return;
|
|
@@ -165,12 +255,12 @@ const CHECKS = [
|
|
|
165
255
|
],
|
|
166
256
|
[
|
|
167
257
|
'deactivated credentials are still returned',
|
|
168
|
-
async (repo) => {
|
|
258
|
+
async (repo, run) => {
|
|
169
259
|
const check = 'deactivated credentials are still returned';
|
|
170
|
-
const record = credential();
|
|
260
|
+
const record = run.credential();
|
|
171
261
|
await repo.createCredential(record);
|
|
172
262
|
await repo.deactivateCredential(record.credentialId);
|
|
173
|
-
const got = await repo.getCredential(record.credentialId);
|
|
263
|
+
const got = await repo.getCredential(record.credentialId, record.realm);
|
|
174
264
|
if (!got)
|
|
175
265
|
fail(check, 'getCredential hid a deactivated credential; the package decides, not the store');
|
|
176
266
|
if (got.isActive !== false)
|
|
@@ -182,31 +272,49 @@ const CHECKS = [
|
|
|
182
272
|
},
|
|
183
273
|
],
|
|
184
274
|
[
|
|
185
|
-
'listing is by user, narrowed by realm',
|
|
186
|
-
async (repo) => {
|
|
187
|
-
const check = 'listing is by user
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
275
|
+
'listing is by user, narrowed by realm when realms are on',
|
|
276
|
+
async (repo, run) => {
|
|
277
|
+
const check = 'listing is by user';
|
|
278
|
+
const ids = (list) => new Set(list.map((c) => c.credentialId));
|
|
279
|
+
const user = run.user(0);
|
|
280
|
+
if (!run.realms) {
|
|
281
|
+
const mine = run.credential({ userId: user });
|
|
282
|
+
const otherUser = run.credential({ userId: run.user(1) });
|
|
283
|
+
for (const c of [mine, otherUser])
|
|
284
|
+
await repo.createCredential(c);
|
|
285
|
+
const listed = ids(await repo.listCredentials(user));
|
|
286
|
+
if (!listed.has(mine.credentialId))
|
|
287
|
+
fail(check, "listCredentials(user) left out the user's passkey");
|
|
288
|
+
if (listed.has(otherUser.credentialId))
|
|
289
|
+
fail(check, "listCredentials(user) returned another user's passkey");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const mine = run.credential({ userId: user, realm: 'realm-a' });
|
|
293
|
+
const otherRealm = run.credential({ userId: user, realm: 'realm-b' });
|
|
294
|
+
const otherUser = run.credential({ userId: run.user(1), realm: 'realm-a' });
|
|
192
295
|
for (const c of [mine, otherRealm, otherUser])
|
|
193
296
|
await repo.createCredential(c);
|
|
194
|
-
const inRealm = (await repo.listCredentials(
|
|
195
|
-
if (inRealm.
|
|
196
|
-
fail(check,
|
|
297
|
+
const inRealm = ids(await repo.listCredentials(user, 'realm-a'));
|
|
298
|
+
if (!inRealm.has(mine.credentialId))
|
|
299
|
+
fail(check, "listCredentials(user, 'realm-a') left out the user's passkey");
|
|
300
|
+
if (inRealm.has(otherRealm.credentialId)) {
|
|
301
|
+
fail(check, "listCredentials(user, 'realm-a') returned the user's passkey from realm-b");
|
|
197
302
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
303
|
+
if (inRealm.has(otherUser.credentialId))
|
|
304
|
+
fail(check, "listCredentials(user, 'realm-a') returned another user's passkey");
|
|
305
|
+
const all = ids(await repo.listCredentials(user));
|
|
306
|
+
if (!all.has(mine.credentialId) || !all.has(otherRealm.credentialId)) {
|
|
307
|
+
fail(check, 'listCredentials(user) without a realm left out one of the user’s passkeys');
|
|
202
308
|
}
|
|
309
|
+
if (all.has(otherUser.credentialId))
|
|
310
|
+
fail(check, "listCredentials(user) returned another user's passkey");
|
|
203
311
|
},
|
|
204
312
|
],
|
|
205
313
|
[
|
|
206
314
|
'updateCounter is a compare-and-set, under concurrency',
|
|
207
|
-
async (repo) => {
|
|
315
|
+
async (repo, run) => {
|
|
208
316
|
const check = 'updateCounter is a compare-and-set';
|
|
209
|
-
const record = credential({ counter: 3 });
|
|
317
|
+
const record = run.credential({ counter: 3 });
|
|
210
318
|
await repo.createCredential(record);
|
|
211
319
|
const update = (expectedCounter, newCounter) => repo.updateCounter({
|
|
212
320
|
credentialId: record.credentialId,
|
|
@@ -223,7 +331,7 @@ const CHECKS = [
|
|
|
223
331
|
fail(check, `${applied} of ${CONCURRENCY} concurrent updates from the same counter applied. It must be one ` +
|
|
224
332
|
'conditional update (UPDATE … WHERE counter = expected), not a read then a write.');
|
|
225
333
|
}
|
|
226
|
-
const got = await repo.getCredential(record.credentialId);
|
|
334
|
+
const got = await repo.getCredential(record.credentialId, record.realm);
|
|
227
335
|
if (!got || got.counter < 4)
|
|
228
336
|
fail(check, `the counter is ${String(got?.counter)} after an applied update`);
|
|
229
337
|
if (got.backedUp !== false)
|
|
@@ -234,11 +342,11 @@ const CHECKS = [
|
|
|
234
342
|
],
|
|
235
343
|
[
|
|
236
344
|
'renameCredential changes the name',
|
|
237
|
-
async (repo) => {
|
|
238
|
-
const record = credential();
|
|
345
|
+
async (repo, run) => {
|
|
346
|
+
const record = run.credential();
|
|
239
347
|
await repo.createCredential(record);
|
|
240
348
|
await repo.renameCredential(record.credentialId, 'Renamed');
|
|
241
|
-
const got = await repo.getCredential(record.credentialId);
|
|
349
|
+
const got = await repo.getCredential(record.credentialId, record.realm);
|
|
242
350
|
if (got?.name !== 'Renamed') {
|
|
243
351
|
fail('renameCredential changes the name', `name is ${JSON.stringify(got?.name)}`);
|
|
244
352
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"passkey-repository.contract.js","sourceRoot":"","sources":["../../src/testing/passkey-repository.contract.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"passkey-repository.contract.js","sourceRoot":"","sources":["../../src/testing/passkey-repository.contract.ts"],"names":[],"mappings":";;;AA6GA,0EA2CC;AAxJD,mCAAoC;AACpC,6FAKoD;AAGpD,MAAa,8BAA+B,SAAQ,KAAK;IACvD,YAAY,KAAa,EAAE,MAAc,EAAE,OAA6B;QACtE,KAAK,CAAC,iCAAiC,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;QAC7C,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,IAA4B,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACtD,CAAC;IACH,CAAC;CACF;AARD,wEAQC;AAiDD,MAAM,WAAW,GAAG,CAAC,CAAC;AAGtB,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAwCzB,KAAK,UAAU,+BAA+B,CACnD,OAAgB,EAChB,UAA4C,EAAE;IAE9C,eAAe,CAAC,OAAO,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IAErC,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;YACnC,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC;gBAC9C,MAAM,KAAK,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,IAAI,KAAK,YAAY,8BAA8B;oBAAE,MAAM,KAAK,CAAC;gBACjE,MAAM,IAAI,8BAA8B,CACtC,IAAI,EACJ,SAAS,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACtF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,OAAO,GAAG,KAAK,CAAC;IAClB,CAAC;IAED,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,YAAqB,EAAE,CAAC;YAC/B,IAAI,OAAO,YAAY,8BAA8B,EAAE,CAAC;gBACtD,MAAM,IAAI,8BAA8B,CACtC,SAAS,EACT,GAAG,OAAO,CAAC,OAAO,8BAChB,YAAY,YAAY,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAC5E,EAAE,EACF,EAAE,KAAK,EAAE,YAAY,EAAE,CACxB,CAAC;YACJ,CAAC;YACD,IAAI,OAAO,KAAK,SAAS;gBAAE,MAAM,YAAY,CAAC;QAChD,CAAC;IACH,CAAC;IAED,IAAI,OAAO,KAAK,SAAS;QAAE,MAAM,OAAO,CAAC;AAC3C,CAAC;AAED,SAAS,eAAe,CAAC,OAAyC;IAChE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAC7C,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,KAAK,SAAS,EAAE,CAAC;QACxD,MAAM,IAAI,SAAS,CAAC,uEAAuE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3D,MAAM,IAAI,SAAS,CAAC,6DAA6D,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAClD,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC;YAChH,MAAM,IAAI,SAAS,CACjB,6FAA6F;gBAC3F,4BAA4B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CACxD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,SAAS,CACjB,+FAA+F;gBAC7F,6FAA6F;gBAC7F,8CAA8C,CACjD,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAGD,MAAM,WAAW;IAMf,YAAY,OAAyC;QAHpC,kBAAa,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,iBAAY,GAAG,IAAI,GAAG,EAAU,CAAC;QAGhD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC;QACrC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAC/B,CAAC;IAGD,IAAI,CAAC,KAAY;QACf,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,IAAI,iBAAiB,IAAA,mBAAU,GAAE,EAAE,CAAC;IAChE,CAAC;IAGD,KAAK,CAAC,KAAa;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,CAAC;IAED,OAAO;QACL,OAAO,EAAE,aAAa,EAAE,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;IAC1F,CAAC;IAOD,KAAK,CAAC,IAAwB;QAC5B,OAAO,IAAI,KAAK,CAAC,IAAI,EAAE;YACrB,GAAG,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;gBAClC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBACtD,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;oBAClC,OAAO,KAAK,EAAE,KAAkD,EAAE,EAAE;wBAClE,MAAM,EAAE,GAAG,MAAO,KAA8C,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;wBACrF,IAAI,OAAO,EAAE,KAAK,QAAQ;4BAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;wBACtD,OAAO,EAAE,CAAC;oBACZ,CAAC,CAAC;gBACJ,CAAC;gBACD,IAAI,QAAQ,KAAK,kBAAkB,EAAE,CAAC;oBACpC,OAAO,KAAK,EAAE,MAA+B,EAAE,EAAE;wBAC/C,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;wBAC5C,OAAQ,KAAgD,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;oBAChF,CAAC,CAAC;gBACJ,CAAC;gBACD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAClE,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED,UAAU,CAAC,YAA8C,EAAE;QACzD,OAAO;YACL,YAAY,EAAE,YAAY,IAAA,mBAAU,GAAE,EAAE;YACxC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACpB,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC;YAC/B,SAAS,EAAE,gBAAgB;YAC3B,OAAO,EAAE,CAAC;YACV,UAAU,EAAE,UAAU,IAAA,mBAAU,GAAE,EAAE;YACpC,UAAU,EAAE,CAAC,UAAU,EAAE,QAAQ,CAAC;YAClC,UAAU,EAAE,aAAa;YACzB,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,sCAAsC;YAC9C,IAAI,EAAE,kBAAkB;YACxB,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;YACzD,UAAU,EAAE,IAAI;YAChB,QAAQ,EAAE,IAAI;YACd,GAAG,SAAS;SACb,CAAC;IACJ,CAAC;CACF;AAED,SAAS,IAAI,CAAC,KAAa,EAAE,MAAc;IACzC,MAAM,IAAI,8BAA8B,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,CAA0B,EAAE,CAA0B,EAAE,EAAE,CAC1E,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AAEpG,MAAM,MAAM,GAAmF;IAC7F;QACE,mCAAmC;QACnC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,mCAAmC,CAAC;YAClD,MAAM,KAAK,GAAG;gBACZ,SAAS,EAAE,aAAa,IAAA,mBAAU,GAAE,EAAE;gBACtC,OAAO,EAAE,cAAuB;gBAChC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;gBACnB,GAAG,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC;gBAC9B,UAAU,EAAE,UAAU,IAAA,mBAAU,GAAE,EAAE;gBACpC,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;aACzC,CAAC;YACF,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;YAC5C,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,EAAE;gBAAE,IAAI,CAAC,KAAK,EAAE,2BAA2B,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACtG,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC5C,IAAI,CAAC,GAAG;gBAAE,IAAI,CAAC,KAAK,EAAE,sDAAsD,CAAC,CAAC;YAC9E,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAU,CAAC;YACzG,KAAK,MAAM,GAAG,IAAI,IAAsE,EAAE,CAAC;gBACzF,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC5B,IAAI,CAAC,KAAK,EAAE,GAAG,GAAG,iBAAiB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;gBACvG,CAAC;YACH,CAAC;YACD,IAAI,GAAG,CAAC,WAAW,KAAK,EAAE;gBAAE,IAAI,CAAC,KAAK,EAAE,4BAA4B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;YACvG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;QAClF,CAAC;KACF;IACD;QACE,wDAAwD;QACxD,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,wDAAwD,CAAC;YACvE,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC;gBACnC,SAAS,EAAE,GAAG;gBACd,OAAO,EAAE,gBAAgB;gBACzB,GAAG,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC;gBAC9B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;aACzC,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CACrE,CAAC;YACF,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;YAC1C,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;gBACd,IAAI,CACF,KAAK,EACL,GAAG,GAAG,OAAO,WAAW,+DAA+D;oBACrF,sGAAsG,CACzG,CAAC;YACJ,CAAC;YACD,IAAI,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,yCAAyC,CAAC,CAAC;QAC9F,CAAC;KACF;IACD;QACE,4CAA4C;QAC5C,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,4CAA4C,CAAC;YAC3D,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC;gBACnC,SAAS,EAAE,GAAG;gBACd,OAAO,EAAE,gBAAgB;gBACzB,GAAG,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC;gBAC9B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;aACxC,CAAC,CAAC;YACH,IAAI,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,EAAE,CAAC;gBACpC,IAAI,CAAC,KAAK,EAAE,uFAAuF,CAAC,CAAC;YACvG,CAAC;YACD,IAAI,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAA,mBAAU,GAAE,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,2CAA2C,CAAC,CAAC;QAC1G,CAAC;KACF;IACD;QACE,0DAA0D;QAC1D,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,oCAAoC,CAAC;YACnD,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;YAC1F,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACxE,IAAI,CAAC,GAAG;gBAAE,IAAI,CAAC,KAAK,EAAE,2DAA2D,CAAC,CAAC;YACnF,MAAM,IAAI,GAAG;gBACX,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY;gBAC9D,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;gBACtD,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aACxB,CAAC;YACX,KAAK,MAAM,GAAG,IAAI,IAAkD,EAAE,CAAC;gBACrE,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC7B,IAAI,CACF,KAAK,EACL,GAAG,GAAG,iBAAiB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE;wBACtF,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,0CAA0C,CAAC,CAAC,CAAC,EAAE,CAAC,CACxE,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;gBACzE,IAAI,CAAC,KAAK,EAAE,2BAA2B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;YAC3E,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;YACjF,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI;gBAAE,IAAI,CAAC,KAAK,EAAE,2BAA2B,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;QAC7G,CAAC;KACF;IACD;QACE,uFAAuF;QACvF,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,sCAAsC,CAAC;YACrD,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;YAClC,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;YACtC,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC5G,CAAC;YAAC,OAAO,KAAc,EAAE,CAAC;gBACxB,IAAI,CAAC,CAAC,KAAK,YAAY,2DAA4B,CAAC,EAAE,CAAC;oBACrD,IAAI,CACF,KAAK,EACL,SAAS,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B;wBACzF,0DAA0D,CAC7D,CAAC;gBACJ,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC7E,IAAI,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC,MAAM;oBAAE,IAAI,CAAC,KAAK,EAAE,yCAAyC,CAAC,CAAC;gBAC7F,OAAO;YACT,CAAC;YACD,IAAI,CAAC,KAAK,EAAE,sEAAsE,CAAC,CAAC;QACtF,CAAC;KACF;IACD;QACE,4CAA4C;QAC5C,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,4CAA4C,CAAC;YAC3D,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;YAChC,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YACrD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACxE,IAAI,CAAC,GAAG;gBAAE,IAAI,CAAC,KAAK,EAAE,gFAAgF,CAAC,CAAC;YACxG,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK;gBAAE,IAAI,CAAC,KAAK,EAAE,oDAAoD,CAAC,CAAC;YAC9F,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;gBAChE,IAAI,CAAC,KAAK,EAAE,8CAA8C,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;KACF;IACD;QACE,0DAA0D;QAC1D,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,oBAAoB,CAAC;YAEnC,MAAM,GAAG,GAAG,CAAC,IAA+B,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;YAC1F,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAEzB,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC9C,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC1D,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;oBAAE,MAAM,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;gBAClE,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;gBACrD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC;oBAAE,IAAI,CAAC,KAAK,EAAE,mDAAmD,CAAC,CAAC;gBACrG,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC;oBAAE,IAAI,CAAC,KAAK,EAAE,uDAAuD,CAAC,CAAC;gBAC7G,OAAO;YACT,CAAC;YAED,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YAChE,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YACtE,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;YAC5E,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC;gBAAE,MAAM,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;YAE9E,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;YACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,8DAA8D,CAAC,CAAC;YACjH,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBACzC,IAAI,CAAC,KAAK,EAAE,2EAA2E,CAAC,CAAC;YAC3F,CAAC;YACD,IAAI,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,kEAAkE,CAAC,CAAC;YAEzH,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;YAClD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;gBACrE,IAAI,CAAC,KAAK,EAAE,2EAA2E,CAAC,CAAC;YAC3F,CAAC;YACD,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,uDAAuD,CAAC,CAAC;QAC5G,CAAC;KACF;IACD;QACE,uDAAuD;QACvD,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,KAAK,GAAG,oCAAoC,CAAC;YACnD,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,MAAM,GAAG,CAAC,eAAuB,EAAE,UAAkB,EAAE,EAAE,CAC7D,IAAI,CAAC,aAAa,CAAC;gBACjB,YAAY,EAAE,MAAM,CAAC,YAAY;gBACjC,eAAe;gBACf,UAAU;gBACV,QAAQ,EAAE,KAAK;gBACf,UAAU,EAAE,IAAI,IAAI,EAAE;aACvB,CAAC,CAAC;YAEL,IAAI,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,sCAAsC,CAAC,CAAC;YAC5E,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACnG,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;YAC/C,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;gBAClB,IAAI,CACF,KAAK,EACL,GAAG,OAAO,OAAO,WAAW,oEAAoE;oBAC9F,kFAAkF,CACrF,CAAC;YACJ,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACxE,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,GAAG,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,kBAAkB,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,0BAA0B,CAAC,CAAC;YAC3G,IAAI,GAAG,CAAC,QAAQ,KAAK,KAAK;gBAAE,IAAI,CAAC,KAAK,EAAE,0BAA0B,CAAC,CAAC;YACpE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,YAAY,IAAI,CAAC;gBAAE,IAAI,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAC;QACnF,CAAC;KACF;IACD;QACE,mCAAmC;QACnC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;YAClB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC;YAChC,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;YACpC,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;YAC5D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACxE,IAAI,GAAG,EAAE,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC5B,IAAI,CAAC,mCAAmC,EAAE,WAAW,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YACpF,CAAC;QACH,CAAC;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { Prisma, PrismaClient } from '@prisma/client';
|
|
3
|
+
import {
|
|
4
|
+
IPasskeyRepository,
|
|
5
|
+
PasskeyChallengeRecord,
|
|
6
|
+
PasskeyCredentialExistsError,
|
|
7
|
+
PasskeyCredentialRecord,
|
|
8
|
+
} from '@ambushsoftworks/nestjs-auth-graphql';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* IPasskeyRepository on Prisma, for the models in ./schema.prisma.
|
|
12
|
+
*
|
|
13
|
+
* Verified against PostgreSQL 16 with `assertPasskeyRepositoryContract` from
|
|
14
|
+
* `@ambushsoftworks/nestjs-auth-graphql/testing`, with and without realms and
|
|
15
|
+
* with a foreign key to users. Run that contract against your copy, too:
|
|
16
|
+
*
|
|
17
|
+
* await assertPasskeyRepositoryContract(() => new PrismaPasskeyRepository(prisma), {
|
|
18
|
+
* userIds: [userA.id, userB.id],
|
|
19
|
+
* cleanup: async ({ credentialIds, challengeIds }) => {
|
|
20
|
+
* await prisma.passkeyCredential.deleteMany({ where: { credentialId: { in: credentialIds } } });
|
|
21
|
+
* await prisma.passkeyChallenge.deleteMany({ where: { id: { in: challengeIds } } });
|
|
22
|
+
* },
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* Inject your own PrismaService in place of PrismaClient if you have one.
|
|
26
|
+
*/
|
|
27
|
+
@Injectable()
|
|
28
|
+
export class PrismaPasskeyRepository implements IPasskeyRepository {
|
|
29
|
+
constructor(private readonly prisma: PrismaClient) {}
|
|
30
|
+
|
|
31
|
+
async storeChallenge(input: Omit<PasskeyChallengeRecord, 'challengeId'>): Promise<string> {
|
|
32
|
+
const row = await this.prisma.passkeyChallenge.create({
|
|
33
|
+
data: {
|
|
34
|
+
challenge: input.challenge,
|
|
35
|
+
purpose: input.purpose,
|
|
36
|
+
userId: input.userId ?? null,
|
|
37
|
+
realm: input.realm ?? null,
|
|
38
|
+
userHandle: input.userHandle ?? null,
|
|
39
|
+
expiresAt: input.expiresAt,
|
|
40
|
+
},
|
|
41
|
+
select: { id: true },
|
|
42
|
+
});
|
|
43
|
+
return row.id;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** One conditional update, then a read: never a read followed by a write. */
|
|
47
|
+
async consumeChallenge(challengeId: string): Promise<PasskeyChallengeRecord | null> {
|
|
48
|
+
const { count } = await this.prisma.passkeyChallenge.updateMany({
|
|
49
|
+
where: { id: challengeId, used: false, expiresAt: { gt: new Date() } },
|
|
50
|
+
data: { used: true, usedAt: new Date() },
|
|
51
|
+
});
|
|
52
|
+
if (count !== 1) return null;
|
|
53
|
+
|
|
54
|
+
const row = await this.prisma.passkeyChallenge.findUnique({ where: { id: challengeId } });
|
|
55
|
+
if (!row) return null;
|
|
56
|
+
return {
|
|
57
|
+
challengeId: row.id,
|
|
58
|
+
challenge: row.challenge,
|
|
59
|
+
purpose: row.purpose as PasskeyChallengeRecord['purpose'],
|
|
60
|
+
...(row.userId !== null ? { userId: row.userId } : {}),
|
|
61
|
+
...(row.realm !== null ? { realm: row.realm } : {}),
|
|
62
|
+
...(row.userHandle !== null ? { userHandle: row.userHandle } : {}),
|
|
63
|
+
expiresAt: row.expiresAt,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async deleteExpiredChallenges(): Promise<number> {
|
|
68
|
+
const { count } = await this.prisma.passkeyChallenge.deleteMany({
|
|
69
|
+
where: { expiresAt: { lt: new Date() } },
|
|
70
|
+
});
|
|
71
|
+
return count;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The primary key is the uniqueness check; its violation becomes the package's error. */
|
|
75
|
+
async createCredential(record: PasskeyCredentialRecord): Promise<void> {
|
|
76
|
+
try {
|
|
77
|
+
await this.prisma.passkeyCredential.create({
|
|
78
|
+
data: {
|
|
79
|
+
credentialId: record.credentialId,
|
|
80
|
+
userId: record.userId,
|
|
81
|
+
realm: record.realm ?? null,
|
|
82
|
+
publicKey: record.publicKey,
|
|
83
|
+
counter: BigInt(record.counter),
|
|
84
|
+
userHandle: record.userHandle,
|
|
85
|
+
transports: record.transports ?? [],
|
|
86
|
+
deviceType: record.deviceType,
|
|
87
|
+
backedUp: record.backedUp,
|
|
88
|
+
aaguid: record.aaguid ?? null,
|
|
89
|
+
name: record.name,
|
|
90
|
+
createdAt: record.createdAt,
|
|
91
|
+
lastUsedAt: record.lastUsedAt,
|
|
92
|
+
isActive: record.isActive,
|
|
93
|
+
...(record.metadata !== undefined ? { metadata: record.metadata as Prisma.InputJsonValue } : {}),
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
} catch (error: unknown) {
|
|
97
|
+
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
|
|
98
|
+
throw new PasskeyCredentialExistsError(record.credentialId);
|
|
99
|
+
}
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Returns deactivated credentials too, and any realm: the package decides. */
|
|
105
|
+
async getCredential(credentialId: string): Promise<PasskeyCredentialRecord | null> {
|
|
106
|
+
const row = await this.prisma.passkeyCredential.findUnique({ where: { credentialId } });
|
|
107
|
+
return row ? toRecord(row) : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async listCredentials(userId: string, realm?: string): Promise<PasskeyCredentialRecord[]> {
|
|
111
|
+
const rows = await this.prisma.passkeyCredential.findMany({
|
|
112
|
+
where: { userId, ...(realm !== undefined ? { realm } : {}) },
|
|
113
|
+
orderBy: { createdAt: 'asc' },
|
|
114
|
+
});
|
|
115
|
+
return rows.map(toRecord);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Compare-and-set: applies only while the stored counter is still `expectedCounter`. */
|
|
119
|
+
async updateCounter(input: {
|
|
120
|
+
credentialId: string;
|
|
121
|
+
expectedCounter: number;
|
|
122
|
+
newCounter: number;
|
|
123
|
+
backedUp: boolean;
|
|
124
|
+
lastUsedAt: Date;
|
|
125
|
+
}): Promise<boolean> {
|
|
126
|
+
const { count } = await this.prisma.passkeyCredential.updateMany({
|
|
127
|
+
where: { credentialId: input.credentialId, counter: BigInt(input.expectedCounter) },
|
|
128
|
+
data: {
|
|
129
|
+
counter: BigInt(input.newCounter),
|
|
130
|
+
backedUp: input.backedUp,
|
|
131
|
+
lastUsedAt: input.lastUsedAt,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
return count === 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async renameCredential(credentialId: string, name: string): Promise<void> {
|
|
138
|
+
await this.prisma.passkeyCredential.update({ where: { credentialId }, data: { name } });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async deactivateCredential(credentialId: string): Promise<void> {
|
|
142
|
+
await this.prisma.passkeyCredential.update({ where: { credentialId }, data: { isActive: false } });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
type CredentialRow = Prisma.PasskeyCredentialGetPayload<Record<string, never>>;
|
|
147
|
+
|
|
148
|
+
function toRecord(row: CredentialRow): PasskeyCredentialRecord {
|
|
149
|
+
return {
|
|
150
|
+
credentialId: row.credentialId,
|
|
151
|
+
userId: row.userId,
|
|
152
|
+
...(row.realm !== null ? { realm: row.realm } : {}),
|
|
153
|
+
publicKey: row.publicKey,
|
|
154
|
+
counter: Number(row.counter),
|
|
155
|
+
userHandle: row.userHandle,
|
|
156
|
+
transports: row.transports,
|
|
157
|
+
deviceType: row.deviceType as PasskeyCredentialRecord['deviceType'],
|
|
158
|
+
backedUp: row.backedUp,
|
|
159
|
+
...(row.aaguid !== null ? { aaguid: row.aaguid } : {}),
|
|
160
|
+
name: row.name,
|
|
161
|
+
createdAt: row.createdAt,
|
|
162
|
+
lastUsedAt: row.lastUsedAt,
|
|
163
|
+
isActive: row.isActive,
|
|
164
|
+
...(row.metadata !== null ? { metadata: row.metadata as Record<string, unknown> } : {}),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Reference schema for IPasskeyRepository, as in the README ("Implementing
|
|
2
|
+
// IPasskeyRepository"). Verified against PostgreSQL 16 with
|
|
3
|
+
// `assertPasskeyRepositoryContract`, with and without realms.
|
|
4
|
+
//
|
|
5
|
+
// Merge these two models into your schema, and add the back-relation to your
|
|
6
|
+
// User model:
|
|
7
|
+
//
|
|
8
|
+
// model User {
|
|
9
|
+
// ...
|
|
10
|
+
// passkeyCredentials PasskeyCredential[]
|
|
11
|
+
// }
|
|
12
|
+
|
|
13
|
+
model PasskeyCredential {
|
|
14
|
+
credentialId String @id // base64url, unique across all users
|
|
15
|
+
userId String
|
|
16
|
+
realm String? // set when realms are enabled
|
|
17
|
+
publicKey String // base64url COSE key
|
|
18
|
+
counter BigInt @default(0) // uint32 on the wire; Int is too small
|
|
19
|
+
userHandle String
|
|
20
|
+
transports String[]
|
|
21
|
+
deviceType String
|
|
22
|
+
backedUp Boolean
|
|
23
|
+
aaguid String?
|
|
24
|
+
name String
|
|
25
|
+
createdAt DateTime @default(now())
|
|
26
|
+
lastUsedAt DateTime?
|
|
27
|
+
isActive Boolean @default(true)
|
|
28
|
+
metadata Json?
|
|
29
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
30
|
+
|
|
31
|
+
@@index([userId, realm])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
model PasskeyChallenge {
|
|
35
|
+
id String @id @default(uuid())
|
|
36
|
+
challenge String
|
|
37
|
+
purpose String // registration | authentication | step-up
|
|
38
|
+
userId String? // not a foreign key: sign-in challenges have no user
|
|
39
|
+
realm String?
|
|
40
|
+
userHandle String? // registration only
|
|
41
|
+
expiresAt DateTime
|
|
42
|
+
used Boolean @default(false)
|
|
43
|
+
usedAt DateTime?
|
|
44
|
+
|
|
45
|
+
@@index([expiresAt])
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ambushsoftworks/nestjs-auth-graphql",
|
|
3
|
-
"version": "0.16.0
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Production-grade authentication package for NestJS with GraphQL, supporting JWT, OAuth, email/SMS verification, and biometric auth",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|