@ambushsoftworks/nestjs-auth-graphql 0.17.0 → 0.18.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 +83 -0
- package/README.md +173 -0
- package/dist/auth.module.d.ts +10 -0
- package/dist/auth.module.d.ts.map +1 -1
- package/dist/auth.module.js +41 -3
- package/dist/auth.module.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/exceptions/email-challenge.exceptions.d.ts +8 -0
- package/dist/exceptions/email-challenge.exceptions.d.ts.map +1 -0
- package/dist/exceptions/email-challenge.exceptions.js +21 -0
- package/dist/exceptions/email-challenge.exceptions.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/auth-logger.interface.d.ts +5 -1
- package/dist/interfaces/auth-logger.interface.d.ts.map +1 -1
- package/dist/interfaces/auth-logger.interface.js +4 -0
- package/dist/interfaces/auth-logger.interface.js.map +1 -1
- package/dist/interfaces/email-challenge-repository.interface.d.ts +22 -0
- package/dist/interfaces/email-challenge-repository.interface.d.ts.map +1 -0
- package/dist/interfaces/email-challenge-repository.interface.js +3 -0
- package/dist/interfaces/email-challenge-repository.interface.js.map +1 -0
- package/dist/interfaces/email-service.interface.d.ts +5 -0
- package/dist/interfaces/email-service.interface.d.ts.map +1 -1
- package/dist/interfaces/email-template-renderer.interface.d.ts +7 -0
- package/dist/interfaces/email-template-renderer.interface.d.ts.map +1 -1
- package/dist/interfaces/index.d.ts +1 -0
- package/dist/interfaces/index.d.ts.map +1 -1
- package/dist/interfaces/index.js +1 -0
- package/dist/interfaces/index.js.map +1 -1
- package/dist/interfaces/rate-limiter.interface.d.ts.map +1 -1
- package/dist/repositories/in-memory-email-challenge.repository.d.ts +14 -0
- package/dist/repositories/in-memory-email-challenge.repository.d.ts.map +1 -0
- package/dist/repositories/in-memory-email-challenge.repository.js +55 -0
- package/dist/repositories/in-memory-email-challenge.repository.js.map +1 -0
- package/dist/services/configurable-email.service.d.ts +2 -1
- package/dist/services/configurable-email.service.d.ts.map +1 -1
- package/dist/services/configurable-email.service.js +24 -0
- package/dist/services/configurable-email.service.js.map +1 -1
- package/dist/services/default-email-template-renderer.d.ts +7 -0
- package/dist/services/default-email-template-renderer.d.ts.map +1 -1
- package/dist/services/default-email-template-renderer.js +31 -0
- package/dist/services/default-email-template-renderer.js.map +1 -1
- package/dist/services/email-challenge.service.d.ts +74 -0
- package/dist/services/email-challenge.service.d.ts.map +1 -0
- package/dist/services/email-challenge.service.js +306 -0
- package/dist/services/email-challenge.service.js.map +1 -0
- package/dist/services/passkey-signup-token.service.d.ts.map +1 -1
- package/dist/services/passkey-signup-token.service.js +4 -4
- package/dist/services/passkey-signup-token.service.js.map +1 -1
- package/dist/services/passkey.service.d.ts.map +1 -1
- package/dist/services/passkey.service.js +12 -22
- package/dist/services/passkey.service.js.map +1 -1
- package/dist/testing/email-challenge-repository.contract.d.ts +15 -0
- package/dist/testing/email-challenge-repository.contract.d.ts.map +1 -0
- package/dist/testing/email-challenge-repository.contract.js +287 -0
- package/dist/testing/email-challenge-repository.contract.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +4 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/utils/rate-limit-helpers.d.ts +4 -0
- package/dist/utils/rate-limit-helpers.d.ts.map +1 -0
- package/dist/utils/rate-limit-helpers.js +20 -0
- package/dist/utils/rate-limit-helpers.js.map +1 -0
- package/examples/email-challenge/prisma-email-challenge.repository.ts +85 -0
- package/examples/email-challenge/schema.prisma +24 -0
- package/package.json +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { PrismaClient } from '@prisma/client';
|
|
3
|
+
import {
|
|
4
|
+
EmailChallengeRecord,
|
|
5
|
+
IEmailChallengeRepository,
|
|
6
|
+
} from '@ambushsoftworks/nestjs-auth-graphql';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* IEmailChallengeRepository on Prisma, for the model in ./schema.prisma.
|
|
10
|
+
*
|
|
11
|
+
* Verified against PostgreSQL 16 with `assertEmailChallengeRepositoryContract`
|
|
12
|
+
* from `@ambushsoftworks/nestjs-auth-graphql/testing`. Run that contract
|
|
13
|
+
* against your copy, too, on your real database:
|
|
14
|
+
*
|
|
15
|
+
* await assertEmailChallengeRepositoryContract(() => new PrismaEmailChallengeRepository(prisma), {
|
|
16
|
+
* cleanup: async ({ challengeIds }) => {
|
|
17
|
+
* await prisma.emailChallenge.deleteMany({ where: { id: { in: challengeIds } } });
|
|
18
|
+
* },
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* Inject your own PrismaService in place of PrismaClient if you have one.
|
|
22
|
+
*/
|
|
23
|
+
@Injectable()
|
|
24
|
+
export class PrismaEmailChallengeRepository implements IEmailChallengeRepository {
|
|
25
|
+
constructor(private readonly prisma: PrismaClient) {}
|
|
26
|
+
|
|
27
|
+
async createChallenge(record: EmailChallengeRecord): Promise<void> {
|
|
28
|
+
await this.prisma.emailChallenge.create({
|
|
29
|
+
data: {
|
|
30
|
+
id: record.id,
|
|
31
|
+
emailHash: record.emailHash,
|
|
32
|
+
purposeHash: record.purposeHash,
|
|
33
|
+
codeHash: record.codeHash,
|
|
34
|
+
attempts: record.attempts,
|
|
35
|
+
consumedAt: record.consumedAt,
|
|
36
|
+
expiresAt: record.expiresAt,
|
|
37
|
+
createdAt: record.createdAt,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Whatever its state: the package decides what expired, consumed and exhausted mean. */
|
|
43
|
+
async findChallenge(id: string): Promise<EmailChallengeRecord | null> {
|
|
44
|
+
return this.prisma.emailChallenge.findUnique({ where: { id } });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** One conditional update: never a read followed by a write. */
|
|
48
|
+
async registerAttempt(id: string, maxAttempts: number): Promise<boolean> {
|
|
49
|
+
const { count } = await this.prisma.emailChallenge.updateMany({
|
|
50
|
+
where: { id, consumedAt: null, attempts: { lt: maxAttempts } },
|
|
51
|
+
data: { attempts: { increment: 1 } },
|
|
52
|
+
});
|
|
53
|
+
return count === 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One conditional update: never a read followed by a write. */
|
|
57
|
+
async consumeChallenge(id: string): Promise<boolean> {
|
|
58
|
+
const { count } = await this.prisma.emailChallenge.updateMany({
|
|
59
|
+
where: { id, consumedAt: null },
|
|
60
|
+
data: { consumedAt: new Date() },
|
|
61
|
+
});
|
|
62
|
+
return count === 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async invalidateChallenges(filter: { purposeHash: string; emailHash?: string }): Promise<number> {
|
|
66
|
+
const { count } = await this.prisma.emailChallenge.updateMany({
|
|
67
|
+
where: {
|
|
68
|
+
purposeHash: filter.purposeHash,
|
|
69
|
+
// Only when given. `emailHash: undefined` would mean the same to Prisma,
|
|
70
|
+
// but spelling it out keeps the intent from depending on that.
|
|
71
|
+
...(filter.emailHash !== undefined ? { emailHash: filter.emailHash } : {}),
|
|
72
|
+
consumedAt: null,
|
|
73
|
+
},
|
|
74
|
+
data: { consumedAt: new Date() },
|
|
75
|
+
});
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async deleteExpiredChallenges(): Promise<number> {
|
|
80
|
+
const { count } = await this.prisma.emailChallenge.deleteMany({
|
|
81
|
+
where: { expiresAt: { lt: new Date() } },
|
|
82
|
+
});
|
|
83
|
+
return count;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Reference schema for IEmailChallengeRepository, as in the README ("Email
|
|
2
|
+
// Challenges"). Verified against PostgreSQL 16 with
|
|
3
|
+
// `assertEmailChallengeRepositoryContract`.
|
|
4
|
+
//
|
|
5
|
+
// Merge this model into your schema. It has no relation to your User model, on
|
|
6
|
+
// purpose: these codes go to people who are not users.
|
|
7
|
+
//
|
|
8
|
+
// Nothing in a row is readable. The address, the purpose and the code are
|
|
9
|
+
// keyed hashes computed by the package (64 hex characters each), and the id is
|
|
10
|
+
// 43 random base64url characters.
|
|
11
|
+
|
|
12
|
+
model EmailChallenge {
|
|
13
|
+
id String @id // generated by the package, not by the database
|
|
14
|
+
emailHash String
|
|
15
|
+
purposeHash String
|
|
16
|
+
codeHash String
|
|
17
|
+
attempts Int @default(0)
|
|
18
|
+
consumedAt DateTime? // null while the code can still be used
|
|
19
|
+
expiresAt DateTime
|
|
20
|
+
createdAt DateTime
|
|
21
|
+
|
|
22
|
+
@@index([purposeHash, emailHash])
|
|
23
|
+
@@index([expiresAt])
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ambushsoftworks/nestjs-auth-graphql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|