@ambushsoftworks/nestjs-auth-graphql 0.17.1 → 0.19.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 +99 -0
- package/README.md +363 -1
- package/dist/auth.module.d.ts +16 -0
- package/dist/auth.module.d.ts.map +1 -1
- package/dist/auth.module.js +71 -3
- package/dist/auth.module.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/decorators/allow-guest-session.decorator.d.ts +3 -0
- package/dist/decorators/allow-guest-session.decorator.d.ts.map +1 -0
- package/dist/decorators/allow-guest-session.decorator.js +8 -0
- package/dist/decorators/allow-guest-session.decorator.js.map +1 -0
- package/dist/decorators/current-guest.decorator.d.ts +2 -0
- package/dist/decorators/current-guest.decorator.d.ts.map +1 -0
- package/dist/decorators/current-guest.decorator.js +7 -0
- package/dist/decorators/current-guest.decorator.js.map +1 -0
- 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/exceptions/guest-session.exceptions.d.ts +9 -0
- package/dist/exceptions/guest-session.exceptions.d.ts.map +1 -0
- package/dist/exceptions/guest-session.exceptions.js +20 -0
- package/dist/exceptions/guest-session.exceptions.js.map +1 -0
- package/dist/guards/create-auth-guard.d.ts.map +1 -1
- package/dist/guards/create-auth-guard.js +29 -1
- package/dist/guards/create-auth-guard.js.map +1 -1
- package/dist/guards/guest-session.guard.d.ts +14 -0
- package/dist/guards/guest-session.guard.d.ts.map +1 -0
- package/dist/guards/guest-session.guard.js +63 -0
- package/dist/guards/guest-session.guard.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces/auth-logger.interface.d.ts +10 -1
- package/dist/interfaces/auth-logger.interface.d.ts.map +1 -1
- package/dist/interfaces/auth-logger.interface.js +9 -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/guest-session-repository.interface.d.ts +37 -0
- package/dist/interfaces/guest-session-repository.interface.d.ts.map +1 -0
- package/dist/interfaces/guest-session-repository.interface.js +3 -0
- package/dist/interfaces/guest-session-repository.interface.js.map +1 -0
- package/dist/interfaces/index.d.ts +2 -0
- package/dist/interfaces/index.d.ts.map +1 -1
- package/dist/interfaces/index.js +2 -0
- package/dist/interfaces/index.js.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/repositories/in-memory-guest-session.repository.d.ts +18 -0
- package/dist/repositories/in-memory-guest-session.repository.d.ts.map +1 -0
- package/dist/repositories/in-memory-guest-session.repository.js +83 -0
- package/dist/repositories/in-memory-guest-session.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/guest-session.service.d.ts +80 -0
- package/dist/services/guest-session.service.d.ts.map +1 -0
- package/dist/services/guest-session.service.js +293 -0
- package/dist/services/guest-session.service.js.map +1 -0
- package/dist/services/passkey.service.d.ts.map +1 -1
- package/dist/services/passkey.service.js +8 -20
- 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/guest-session-repository.contract.d.ts +18 -0
- package/dist/testing/guest-session-repository.contract.d.ts.map +1 -0
- package/dist/testing/guest-session-repository.contract.js +368 -0
- package/dist/testing/guest-session-repository.contract.js.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +7 -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/examples/guest-sessions/prisma-guest-session.repository.ts +86 -0
- package/examples/guest-sessions/schema.prisma +33 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -27,6 +27,105 @@ 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.19.0] - 2026-10-08
|
|
31
|
+
|
|
32
|
+
**Guest sessions**: a session for someone who is not a user, which works only on
|
|
33
|
+
routes that opt in, for the things you granted, until the dates you set.
|
|
34
|
+
Additive; nothing changes for an app that does not use it. Requested by Ariadne,
|
|
35
|
+
for outside translators working on one job.
|
|
36
|
+
|
|
37
|
+
### ⚠ External configuration required
|
|
38
|
+
|
|
39
|
+
Only to use guest sessions:
|
|
40
|
+
- **Two tables for `IGuestSessionRepository`**, with no foreign key to users.
|
|
41
|
+
Schema and Prisma adapter in `examples/guest-sessions/`. Run
|
|
42
|
+
`assertGuestSessionRepositoryContract` against it on your real database.
|
|
43
|
+
- **Your frontend handles `GUEST_SESSION_INVALID`** by sending the person to
|
|
44
|
+
prove their address again. It must not be treated like `UNAUTHENTICATED`:
|
|
45
|
+
there is no refresh for a guest.
|
|
46
|
+
- Without `features.cookieAuth`, the client keeps the token `issue` returns and
|
|
47
|
+
sends it as `Authorization: Guest <token>`. **The token changes whenever a
|
|
48
|
+
grant is added**; store the new one.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **`GuestSessionService`**: `issue({ principal, grant })` starts a session or
|
|
53
|
+
adds a grant to the one the browser holds; `revokeGrant({ kind, id })`,
|
|
54
|
+
`revoke(sessionId)` and `end(request, res)` stop them. See the README, "Guest
|
|
55
|
+
Sessions".
|
|
56
|
+
- **`@AllowGuestSession(kind)`**, on a method, lets a guest with a standing
|
|
57
|
+
grant of that kind reach the handler. Guards made by `createAuthGuard` read
|
|
58
|
+
it, trying the user first; so does the new **`GuestSessionGuard`**, which
|
|
59
|
+
admits guests only. **`@CurrentGuest()`** gives the handler the guest and
|
|
60
|
+
their grants of that kind.
|
|
61
|
+
- **`IGuestSessionRepository`**, `InMemoryGuestSessionRepository`, and
|
|
62
|
+
`guestSessionRepositoryInstance`, which enables the feature.
|
|
63
|
+
- **`assertGuestSessionRepositoryContract`** in
|
|
64
|
+
`@ambushsoftworks/nestjs-auth-graphql/testing`.
|
|
65
|
+
- **`guestSession`** options: `maxGrantsPerSession` (default 10; `1` gives one
|
|
66
|
+
grant per session) and `maxLifetimeDays` (default 30).
|
|
67
|
+
- Security events `GUEST_SESSION_ISSUED`, `GUEST_SESSION_REJECTED`,
|
|
68
|
+
`GUEST_SESSION_REVOKED`, `GUEST_SESSION_ENDED` and `GUEST_GRANT_REVOKED`.
|
|
69
|
+
- Error codes `GUEST_SESSION_INVALID` (401) and `GUEST_SESSION_UNAVAILABLE`
|
|
70
|
+
(503).
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- A guard made by `createAuthGuard` now reads `@AllowGuestSession` on the
|
|
75
|
+
handler. On a handler without it, which is every handler in an existing app,
|
|
76
|
+
nothing changes: no guest credential is read. `JwtAuthGuard` is unchanged and
|
|
77
|
+
ignores the decorator.
|
|
78
|
+
|
|
79
|
+
## [0.18.0] - 2026-10-07
|
|
80
|
+
|
|
81
|
+
**Email challenges**: one-time codes that prove someone holds an email address,
|
|
82
|
+
whether or not it belongs to a user. Additive; nothing changes for an app that
|
|
83
|
+
does not use it. Requested by Ariadne, for outside translators who open one job
|
|
84
|
+
and are not users.
|
|
85
|
+
|
|
86
|
+
### ⚠ External configuration required
|
|
87
|
+
|
|
88
|
+
Only to use email challenges:
|
|
89
|
+
- **A table for `IEmailChallengeRepository`**, with no foreign key to users.
|
|
90
|
+
Schema and Prisma adapter in `examples/email-challenge/`. Run
|
|
91
|
+
`assertEmailChallengeRepositoryContract` against it on your real database.
|
|
92
|
+
- **An email service that can send the code.** `ConfigurableEmailService` does,
|
|
93
|
+
with the default renderer. A custom `IEmailTemplateRenderer` adds
|
|
94
|
+
`renderEmailChallengeCodeEmail`; a hand-written `IEmailService` adds
|
|
95
|
+
`sendEmailChallengeCode`.
|
|
96
|
+
- **A shared `rateLimiterInstance`** if you run more than one instance: the send
|
|
97
|
+
limits are counted there.
|
|
98
|
+
- **A button in your frontend** that asks for the code. Do not send it when the
|
|
99
|
+
page loads: mail scanners open links.
|
|
100
|
+
|
|
101
|
+
### Added
|
|
102
|
+
|
|
103
|
+
- **`EmailChallengeService`**: `issue({ email, purpose, emailData? })` emails a
|
|
104
|
+
6-digit code and returns an opaque `challengeId`; `verify({ challengeId, code,
|
|
105
|
+
email, purpose })` answers `{ ok: true, email, purpose }` or `{ ok: false,
|
|
106
|
+
reason: 'WRONG' | 'EXPIRED' | 'LOCKED' }`; `revoke({ purpose, email? })` stops
|
|
107
|
+
outstanding codes. No `perform*` resolver methods: your resolver calls it with
|
|
108
|
+
an address and purpose from your own data. See the README, "Email Challenges",
|
|
109
|
+
including six rules for that resolver.
|
|
110
|
+
- **`IEmailChallengeRepository`**, `InMemoryEmailChallengeRepository`, and
|
|
111
|
+
`emailChallengeRepositoryInstance`, which enables the feature. Rows hold keyed
|
|
112
|
+
hashes only: no address, purpose or code.
|
|
113
|
+
- **`assertEmailChallengeRepositoryContract`** in
|
|
114
|
+
`@ambushsoftworks/nestjs-auth-graphql/testing`.
|
|
115
|
+
- **`emailChallenge`** options: `ttlMinutes` (default 15, up to 60) and
|
|
116
|
+
`issueLimits` (`cooldownSeconds` 60, `perHour` 5, `perIpPerHour` 30).
|
|
117
|
+
- `IEmailService.sendEmailChallengeCode` and
|
|
118
|
+
`IEmailTemplateRenderer.renderEmailChallengeCodeEmail`, both optional.
|
|
119
|
+
- Security events `EMAIL_CHALLENGE_ISSUED`, `EMAIL_CHALLENGE_VERIFIED`,
|
|
120
|
+
`EMAIL_CHALLENGE_FAILED` and `EMAIL_CHALLENGE_LOCKED`.
|
|
121
|
+
- Error codes `EMAIL_CHALLENGE_UNAVAILABLE` and `EMAIL_CHALLENGE_SEND_FAILED`
|
|
122
|
+
(both 503).
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- The boot warning for a missing `rateLimiterInstance` now names everything
|
|
127
|
+
counted there, not only password-reset throttling.
|
|
128
|
+
|
|
30
129
|
## [0.17.1] - 2026-10-07
|
|
31
130
|
|
|
32
131
|
A security fix for passwordless sign-up, which shipped in 0.17.0. **If you offer
|
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ Production-grade authentication module for NestJS GraphQL + REST APIs. JWT, cook
|
|
|
21
21
|
- [Biometric Authentication](#biometric-authentication)
|
|
22
22
|
- [Passkeys](#passkeys)
|
|
23
23
|
- [Step-Up Re-authentication](#step-up-re-authentication)
|
|
24
|
+
- [Email Challenges](#email-challenges)
|
|
25
|
+
- [Guest Sessions](#guest-sessions)
|
|
24
26
|
- [Brute Force Protection](#brute-force-protection)
|
|
25
27
|
- [Password Reset](#password-reset)
|
|
26
28
|
- [Password Policy](#password-policy)
|
|
@@ -42,6 +44,8 @@ Production-grade authentication module for NestJS GraphQL + REST APIs. JWT, cook
|
|
|
42
44
|
- [Biometric Options (`biometric`)](#biometric-options-biometric)
|
|
43
45
|
- [Passkey Options (`passkey`)](#passkey-options-passkey)
|
|
44
46
|
- [Step-Up Options (`stepUp`)](#step-up-options-stepup)
|
|
47
|
+
- [Email Challenge Options (`emailChallenge`)](#email-challenge-options-emailchallenge)
|
|
48
|
+
- [Guest Session Options (`guestSession`)](#guest-session-options-guestsession)
|
|
45
49
|
- [SendGrid Options (`sendgrid`)](#sendgrid-options-sendgrid)
|
|
46
50
|
- [Twilio Options (`twilio`)](#twilio-options-twilio)
|
|
47
51
|
- [Optional Instance Options](#optional-instance-options)
|
|
@@ -1465,6 +1469,337 @@ over a short expiry. Shorten `stepUp.ttlSeconds` if you want a tighter window.
|
|
|
1465
1469
|
**Email codes use a new verification type, `step-up`.** If your verification
|
|
1466
1470
|
repository stores the type in a database enum, add the value.
|
|
1467
1471
|
|
|
1472
|
+
### Email Challenges
|
|
1473
|
+
|
|
1474
|
+
Prove that someone holds an email address, with a 6-digit code, **whether or not
|
|
1475
|
+
the address belongs to a user**: an outside translator opening a job, a referee
|
|
1476
|
+
uploading a letter, a client's employer confirming details. Since 0.18.0.
|
|
1477
|
+
|
|
1478
|
+
There are no resolver methods for this. Your own resolver calls
|
|
1479
|
+
`EmailChallengeService`:
|
|
1480
|
+
|
|
1481
|
+
```typescript
|
|
1482
|
+
@Resolver()
|
|
1483
|
+
export class TranslationJobResolver {
|
|
1484
|
+
constructor(
|
|
1485
|
+
private readonly emailChallenges: EmailChallengeService,
|
|
1486
|
+
private readonly jobs: JobsService,
|
|
1487
|
+
) {}
|
|
1488
|
+
|
|
1489
|
+
// The link in the job email carries a job reference and no credential.
|
|
1490
|
+
// "Send me a code" is a button on the page it opens.
|
|
1491
|
+
@Mutation(() => JobCodeSent)
|
|
1492
|
+
async sendJobCode(@Args('jobRef') jobRef: string, @CurrentRealm() realm: string, @Context() ctx) {
|
|
1493
|
+
const job = await this.jobs.findByReference(jobRef); // throws if unknown
|
|
1494
|
+
return this.emailChallenges.issue(
|
|
1495
|
+
{ email: job.translatorEmail, purpose: `translation-job:${job.id}`, emailData: { firm: job.firmName } },
|
|
1496
|
+
{ realm, ipAddress: extractIpAddress(ctx.req) },
|
|
1497
|
+
); // { challengeId, expiresAt, expiresInMinutes }
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
@Mutation(() => JobOpened)
|
|
1501
|
+
async openJob(@Args('input') input: OpenJobInput, @CurrentRealm() realm: string, @Context() ctx) {
|
|
1502
|
+
const job = await this.jobs.findByReference(input.jobRef);
|
|
1503
|
+
const result = await this.emailChallenges.verify(
|
|
1504
|
+
// The address and purpose this job says the code must be for.
|
|
1505
|
+
{ challengeId: input.challengeId, code: input.code, email: job.translatorEmail, purpose: `translation-job:${job.id}` },
|
|
1506
|
+
{ realm, ipAddress: extractIpAddress(ctx.req) },
|
|
1507
|
+
);
|
|
1508
|
+
if (!result.ok) return { ok: false, reason: result.reason }; // 'WRONG' | 'EXPIRED' | 'LOCKED'
|
|
1509
|
+
// result.email holds mail for this job. What they get now is yours to decide.
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
```
|
|
1513
|
+
|
|
1514
|
+
**Enable it** with a repository and an email service that can send the code:
|
|
1515
|
+
|
|
1516
|
+
```typescript
|
|
1517
|
+
AuthModule.forRootAsync({
|
|
1518
|
+
useFactory: (emailChallenges: PrismaEmailChallengeRepository) => ({
|
|
1519
|
+
// ...
|
|
1520
|
+
emailChallengeRepositoryInstance: emailChallenges, // enables the feature; there is no flag
|
|
1521
|
+
rateLimiterInstance: sharedRateLimiter, // send limits; shared if you run several instances
|
|
1522
|
+
}),
|
|
1523
|
+
})
|
|
1524
|
+
```
|
|
1525
|
+
|
|
1526
|
+
`ConfigurableEmailService` sends the code through `renderEmailChallengeCodeEmail`
|
|
1527
|
+
on its renderer; the default renderer has a generic one. A hand-written
|
|
1528
|
+
`IEmailService` adds `sendEmailChallengeCode(email, code, expiresInMinutes, { purpose, data })`.
|
|
1529
|
+
Without the repository or the method, `issue` answers `EMAIL_CHALLENGE_UNAVAILABLE`.
|
|
1530
|
+
|
|
1531
|
+
**What a verified challenge proves:** that whoever entered the code could read
|
|
1532
|
+
mail sent to that address in the last 15 minutes. Not who they are, and nothing
|
|
1533
|
+
about any account.
|
|
1534
|
+
|
|
1535
|
+
| Behaviour | Detail |
|
|
1536
|
+
|---|---|
|
|
1537
|
+
| Code | 6 digits, 15 minutes (`emailChallenge.ttlMinutes`, or `ttlMinutes` per call; 1 to 60) |
|
|
1538
|
+
| Attempts | Three per code, right or wrong, then `LOCKED`. A new code can be sent |
|
|
1539
|
+
| Single use | A code is accepted once. Two requests with the right code: one wins |
|
|
1540
|
+
| Newest wins | Sending a new code stops the earlier one for that address and purpose |
|
|
1541
|
+
| Binding | `verify` refuses a challenge issued for another address, purpose or realm, as `EXPIRED`. Purposes never affect each other, nor a user's own email verification |
|
|
1542
|
+
| Send limits | Per address and purpose: one a minute, five an hour. Per IP: thirty an hour. `RATE_LIMIT_EXCEEDED` with `retryAfterSeconds`; see [`emailChallenge`](#email-challenge-options-emailchallenge) |
|
|
1543
|
+
| A failed send | `EMAIL_CHALLENGE_SEND_FAILED` and nothing else: provider errors quote the recipient. The detail goes to `IAuthLogger.error`. Costs the address a minute's wait, not its hourly five |
|
|
1544
|
+
| Revoking | `revoke({ purpose, email? })` stops outstanding codes, for when the job is cancelled |
|
|
1545
|
+
|
|
1546
|
+
| Result or code | Meaning | Tell the person |
|
|
1547
|
+
|---|---|---|
|
|
1548
|
+
| `{ ok: true, email, purpose }` | The code was right | |
|
|
1549
|
+
| `reason: 'WRONG'` | Not the code; attempts remain | Try again |
|
|
1550
|
+
| `reason: 'EXPIRED'` | Ran out, already used, replaced, or not a challenge for this address and purpose | Send a new code |
|
|
1551
|
+
| `reason: 'LOCKED'` | Three attempts spent | Send a new code |
|
|
1552
|
+
| `RATE_LIMIT_EXCEEDED` (429) | Too many codes requested | Wait `retryAfterSeconds` |
|
|
1553
|
+
| `EMAIL_CHALLENGE_SEND_FAILED` (503) | The email could not be sent | Try again shortly |
|
|
1554
|
+
| `EMAIL_CHALLENGE_UNAVAILABLE` (503) | Not configured | |
|
|
1555
|
+
|
|
1556
|
+
A refused code is a result, not an exception: what to show is your decision.
|
|
1557
|
+
The exact cause (`code_wrong`, `purpose_mismatch`, `challenge_expired`, ...) is
|
|
1558
|
+
in the `EMAIL_CHALLENGE_FAILED` security event only.
|
|
1559
|
+
|
|
1560
|
+
#### Rules for your resolver
|
|
1561
|
+
|
|
1562
|
+
`issue` sends mail to whatever address it is given, and the person asking may
|
|
1563
|
+
not be the address's owner. Six rules keep that safe:
|
|
1564
|
+
|
|
1565
|
+
1. **Take the address and the purpose from your own data, never from the
|
|
1566
|
+
request.** `verify` then enforces that the code was issued for exactly that.
|
|
1567
|
+
2. **Send the code on a button press, not when the page loads.** Mail scanners
|
|
1568
|
+
and link previews open links, and each would send a code and spend the
|
|
1569
|
+
cooldown.
|
|
1570
|
+
3. **Make the reference in the link unguessable.** Whoever holds the link can
|
|
1571
|
+
spend the address's five codes an hour, which locks the real person out for
|
|
1572
|
+
that hour. They still cannot read the code.
|
|
1573
|
+
4. **Pass `ipAddress`.** Without it the per-IP limit is not applied (the service
|
|
1574
|
+
warns once).
|
|
1575
|
+
5. **Keep personal data out of `purpose`.** It is written to security events as
|
|
1576
|
+
given. Use ids. Put a counter in it (`translation-job:42:2`) if a re-sent job
|
|
1577
|
+
should start with a clean slate.
|
|
1578
|
+
6. **Show the address masked** (`t•••@agency.example`), not in full: the person
|
|
1579
|
+
looking at the page may be holding a forwarded link.
|
|
1580
|
+
|
|
1581
|
+
#### Implementing `IEmailChallengeRepository`
|
|
1582
|
+
|
|
1583
|
+
One table, no relation to your users. Reference schema and Prisma adapter:
|
|
1584
|
+
[`examples/email-challenge/`](examples/email-challenge/).
|
|
1585
|
+
|
|
1586
|
+
```prisma
|
|
1587
|
+
model EmailChallenge {
|
|
1588
|
+
id String @id // generated by the package
|
|
1589
|
+
emailHash String
|
|
1590
|
+
purposeHash String
|
|
1591
|
+
codeHash String
|
|
1592
|
+
attempts Int @default(0)
|
|
1593
|
+
consumedAt DateTime?
|
|
1594
|
+
expiresAt DateTime
|
|
1595
|
+
createdAt DateTime
|
|
1596
|
+
|
|
1597
|
+
@@index([purposeHash, emailHash])
|
|
1598
|
+
@@index([expiresAt])
|
|
1599
|
+
}
|
|
1600
|
+
```
|
|
1601
|
+
|
|
1602
|
+
Nothing in a row is readable. The address, the purpose and the code are stored
|
|
1603
|
+
as keyed hashes the package computes (the realm is inside the first two), so
|
|
1604
|
+
the table holds no addresses of people who are not your users, and the adapter
|
|
1605
|
+
has no realm to filter on.
|
|
1606
|
+
|
|
1607
|
+
**Two methods must each be one conditional write**, never a read followed by a
|
|
1608
|
+
write: `registerAttempt` (`UPDATE … SET attempts = attempts + 1 WHERE attempts <
|
|
1609
|
+
max AND consumedAt IS NULL`) and `consumeChallenge` (`UPDATE … WHERE consumedAt
|
|
1610
|
+
IS NULL`). A read-then-write version lets guesses sent together all count as
|
|
1611
|
+
one, or one code be accepted twice. Check your adapter against your real
|
|
1612
|
+
database:
|
|
1613
|
+
|
|
1614
|
+
```typescript
|
|
1615
|
+
import { assertEmailChallengeRepositoryContract } from '@ambushsoftworks/nestjs-auth-graphql/testing';
|
|
1616
|
+
|
|
1617
|
+
it('meets the email challenge repository contract', async () => {
|
|
1618
|
+
await assertEmailChallengeRepositoryContract(() => new PrismaEmailChallengeRepository(prisma), {
|
|
1619
|
+
cleanup: async ({ challengeIds }) => {
|
|
1620
|
+
await prisma.emailChallenge.deleteMany({ where: { id: { in: challengeIds } } });
|
|
1621
|
+
},
|
|
1622
|
+
});
|
|
1623
|
+
});
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
It calls `deleteExpiredChallenges`, which removes every expired challenge in
|
|
1627
|
+
that database. `InMemoryEmailChallengeRepository` is for tests and a single
|
|
1628
|
+
instance.
|
|
1629
|
+
|
|
1630
|
+
### Guest Sessions
|
|
1631
|
+
|
|
1632
|
+
A session for someone who is **not a user**: it works only on the routes you
|
|
1633
|
+
mark, for the things you granted, until the dates you set. For the outside
|
|
1634
|
+
translator who has proved their address with an [email challenge](#email-challenges)
|
|
1635
|
+
and now needs to work on one job. Since 0.19.0.
|
|
1636
|
+
|
|
1637
|
+
```typescript
|
|
1638
|
+
// 1. After your own check that they hold the address (a verified email challenge):
|
|
1639
|
+
const issued = await this.guestSessions.issue(
|
|
1640
|
+
{
|
|
1641
|
+
principal: { id: translator.id, email: job.translatorEmail }, // your id for the person
|
|
1642
|
+
grant: { kind: 'translation-job', id: job.id, expiresAt: job.dueAt },
|
|
1643
|
+
},
|
|
1644
|
+
{ request: ctx.req, res: ctx.res, realm, ipAddress: extractIpAddress(ctx.req) },
|
|
1645
|
+
);
|
|
1646
|
+
|
|
1647
|
+
// 2. On the routes a guest may reach, and no others:
|
|
1648
|
+
@Mutation(() => UploadResult)
|
|
1649
|
+
@UseGuards(CsrfGuard, JwtGuard) // a guard from createAuthGuard
|
|
1650
|
+
@AllowGuestSession('translation-job') // on the method
|
|
1651
|
+
upload(@Args('input') input: UploadInput,
|
|
1652
|
+
@CurrentUser() user: User | undefined,
|
|
1653
|
+
@CurrentGuest() guest: GuestPrincipal | undefined) {
|
|
1654
|
+
if (guest && !guest.grants.some((g) => g.id === input.jobId)) throw new ForbiddenException();
|
|
1655
|
+
// ...
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// 3. When the job is delivered or taken back:
|
|
1659
|
+
await this.guestSessions.revokeGrant({ kind: 'translation-job', id: job.id }, { realm });
|
|
1660
|
+
```
|
|
1661
|
+
|
|
1662
|
+
**Enable it** with `guestSessionRepositoryInstance`. There is no flag, and no
|
|
1663
|
+
fallback store.
|
|
1664
|
+
|
|
1665
|
+
#### What a guest can and cannot reach
|
|
1666
|
+
|
|
1667
|
+
| | |
|
|
1668
|
+
|---|---|
|
|
1669
|
+
| A handler with `@AllowGuestSession(kind)` | Admitted, if the session holds a standing grant of that kind |
|
|
1670
|
+
| Every other handler | **Refused. The guest credential is not even read** |
|
|
1671
|
+
| `@CurrentUser()`, `request.user` | Empty for a guest. The guest is on `request.guest` / `@CurrentGuest()` |
|
|
1672
|
+
| `PermissionGuard`, `TenantGuard`, `ScopeGuard` | Refuse a guest: there is no user for them to look up |
|
|
1673
|
+
| Token refresh | None. When a grant ends, the person proves their address again |
|
|
1674
|
+
| GraphQL subscriptions | Not accepted |
|
|
1675
|
+
|
|
1676
|
+
`@AllowGuestSession` is read **from the method only**. On a class it does
|
|
1677
|
+
nothing, so decorating a resolver cannot open every method on it.
|
|
1678
|
+
|
|
1679
|
+
| Guard on the route | With `@AllowGuestSession(kind)` |
|
|
1680
|
+
|---|---|
|
|
1681
|
+
| One made by `createAuthGuard([...])` | Users and guests. The user strategies run first, so a signed-in user is handled exactly as on any other route; only if none authenticates is the guest session looked at |
|
|
1682
|
+
| `GuestSessionGuard` | Guests only |
|
|
1683
|
+
| `JwtAuthGuard` | Users only: it ignores the decorator |
|
|
1684
|
+
|
|
1685
|
+
#### What the package checks, and what is yours
|
|
1686
|
+
|
|
1687
|
+
The package guarantees that the guest holds a standing grant of the **kind** the
|
|
1688
|
+
route names. **Which one is your check**, in every opted-in handler: compare
|
|
1689
|
+
what the request asks for with `guest.grants`. A route marked for
|
|
1690
|
+
`translation-job` is reachable by every guest with any translation job.
|
|
1691
|
+
|
|
1692
|
+
Two more things are yours:
|
|
1693
|
+
|
|
1694
|
+
- **What the response contains.** Field resolvers do not run guards by default.
|
|
1695
|
+
A guest-allowed query returning `Job { client { ... } }` exposes whatever
|
|
1696
|
+
those fields resolve. Return guest-shaped types from guest routes.
|
|
1697
|
+
- **Do not make a guest route `@Public()`.** `CsrfGuard` skips public routes,
|
|
1698
|
+
and a guest cookie needs its CSRF check like any other cookie session. That
|
|
1699
|
+
is why the route keeps its normal guard and adds the decorator.
|
|
1700
|
+
|
|
1701
|
+
#### Several grants in one session
|
|
1702
|
+
|
|
1703
|
+
A translator with two open jobs uses one browser. When `issue` is given the
|
|
1704
|
+
request, and that browser already holds a guest session **for the same
|
|
1705
|
+
`principal.id`, address and realm**, the new grant joins it. Otherwise a new
|
|
1706
|
+
session starts.
|
|
1707
|
+
|
|
1708
|
+
| Behaviour | Detail |
|
|
1709
|
+
|---|---|
|
|
1710
|
+
| The token changes when a grant is added | The old one stops working, so an older copy of it never gains the new job. With cookie auth the cookie is replaced for you |
|
|
1711
|
+
| `guestSession.maxGrantsPerSession` (default 10) | When full, the grant that ends soonest is revoked to make room |
|
|
1712
|
+
| `maxGrantsPerSession: 1` | One grant per session: each new one replaces the last |
|
|
1713
|
+
| The same `kind` and `id` again | Replaces the earlier grant, taking the new end date |
|
|
1714
|
+
| Each grant has its own `expiresAt` | Never moved. At most `guestSession.maxLifetimeDays` (default 30) ahead |
|
|
1715
|
+
|
|
1716
|
+
#### Credential and codes
|
|
1717
|
+
|
|
1718
|
+
The credential is an opaque random token, stored only as a keyed hash. It is not
|
|
1719
|
+
a JWT, so nothing that verifies access tokens can accept it.
|
|
1720
|
+
|
|
1721
|
+
- **With `features.cookieAuth`** and `res` passed to `issue`, it is set as the
|
|
1722
|
+
HttpOnly cookie `guest_session` (`__Host-guest_session` with
|
|
1723
|
+
`cookie.useHostPrefix`), with your cookie options, lasting until the session's
|
|
1724
|
+
last grant ends. It is a different cookie from the access token, so a staff
|
|
1725
|
+
member who opens a translator's link stays signed in.
|
|
1726
|
+
- **Otherwise** the client sends `Authorization: Guest <token>`.
|
|
1727
|
+
|
|
1728
|
+
| Answer | When | Tell the person |
|
|
1729
|
+
|---|---|---|
|
|
1730
|
+
| The route's usual `UNAUTHENTICATED` | No credential at all | Sign in, or open your link |
|
|
1731
|
+
| `GUEST_SESSION_INVALID` (401) | A guest credential that cannot be used here: revoked, ended, another realm, another kind, or unknown | Prove your address again |
|
|
1732
|
+
| `GUEST_SESSION_UNAVAILABLE` (503) | `issue` without a repository | |
|
|
1733
|
+
|
|
1734
|
+
`GUEST_SESSION_INVALID` is deliberately not `UNAUTHENTICATED`: clients treat
|
|
1735
|
+
that as "refresh and retry", which can never work for a guest. The same code is
|
|
1736
|
+
in the body on REST routes. The exact cause (`revoked`, `expired`,
|
|
1737
|
+
`realm_mismatch`, `kind_not_allowed`) is in the `GUEST_SESSION_REJECTED`
|
|
1738
|
+
security event.
|
|
1739
|
+
|
|
1740
|
+
`end(request, res)` is the guest's own "I'm done": it revokes the session and
|
|
1741
|
+
clears the cookie. Put `CsrfGuard` on that route too.
|
|
1742
|
+
|
|
1743
|
+
#### Implementing `IGuestSessionRepository`
|
|
1744
|
+
|
|
1745
|
+
Two tables, no relation to your users. Reference schema and Prisma adapter:
|
|
1746
|
+
[`examples/guest-sessions/`](examples/guest-sessions/).
|
|
1747
|
+
|
|
1748
|
+
```prisma
|
|
1749
|
+
model GuestSession {
|
|
1750
|
+
id String @id
|
|
1751
|
+
tokenHash String @unique // read on every guest request
|
|
1752
|
+
principalId String
|
|
1753
|
+
email String
|
|
1754
|
+
realm String?
|
|
1755
|
+
revokedAt DateTime?
|
|
1756
|
+
createdAt DateTime
|
|
1757
|
+
grants GuestGrant[]
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
model GuestGrant {
|
|
1761
|
+
id String @id
|
|
1762
|
+
sessionId String
|
|
1763
|
+
kind String
|
|
1764
|
+
grantId String
|
|
1765
|
+
realm String?
|
|
1766
|
+
expiresAt DateTime
|
|
1767
|
+
revokedAt DateTime?
|
|
1768
|
+
createdAt DateTime
|
|
1769
|
+
session GuestSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
|
1770
|
+
|
|
1771
|
+
@@index([sessionId])
|
|
1772
|
+
@@index([kind, grantId])
|
|
1773
|
+
@@index([expiresAt])
|
|
1774
|
+
}
|
|
1775
|
+
```
|
|
1776
|
+
|
|
1777
|
+
Two rules are the ones adapters get wrong, and the contract checks both:
|
|
1778
|
+
|
|
1779
|
+
- **`rotateToken` is one conditional write** (`UPDATE … SET tokenHash = new WHERE
|
|
1780
|
+
tokenHash = old AND revokedAt IS NULL`), never a read followed by a write.
|
|
1781
|
+
- **`realm: null` means "no realm", never "any realm".** Pass it through as
|
|
1782
|
+
given; `where: { realm: undefined }` drops the condition.
|
|
1783
|
+
|
|
1784
|
+
```typescript
|
|
1785
|
+
import { assertGuestSessionRepositoryContract } from '@ambushsoftworks/nestjs-auth-graphql/testing';
|
|
1786
|
+
|
|
1787
|
+
it('meets the guest session repository contract', async () => {
|
|
1788
|
+
await assertGuestSessionRepositoryContract(() => new PrismaGuestSessionRepository(prisma), {
|
|
1789
|
+
realms: false, // if you do not use realms
|
|
1790
|
+
cleanup: async ({ sessionIds, grantIds }) => {
|
|
1791
|
+
await prisma.guestGrant.deleteMany({ where: { id: { in: grantIds } } });
|
|
1792
|
+
await prisma.guestSession.deleteMany({ where: { id: { in: sessionIds } } });
|
|
1793
|
+
},
|
|
1794
|
+
});
|
|
1795
|
+
});
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
It calls `deleteExpired`, which removes every expired grant and every session
|
|
1799
|
+
without grants in that database. `InMemoryGuestSessionRepository` is for tests
|
|
1800
|
+
and a single instance. The store is read once per request that presents a guest
|
|
1801
|
+
credential on an opted-in route, and never written per request.
|
|
1802
|
+
|
|
1468
1803
|
### Brute Force Protection
|
|
1469
1804
|
|
|
1470
1805
|
Provide a `bruteForceRepositoryInstance` and the module tracks failed login attempts and temporarily locks accounts. Without one, `NoOpBruteForceRepository` is used and no account is ever locked; there is no flag to set. The lockout policy (attempt thresholds, lockout duration) is determined by your `IBruteForceRepository` implementation.
|
|
@@ -1971,6 +2306,26 @@ reported at boot as ignored. With it, `rp` is required.
|
|
|
1971
2306
|
| `ttlSeconds` | `number` | `300` | Step-up token lifetime, 1 to 3600 |
|
|
1972
2307
|
| `issueOnLogin` | `boolean` | `false` | Return a step-up token from fresh sign-ins (see [Step-Up](#step-up-re-authentication)) |
|
|
1973
2308
|
|
|
2309
|
+
### Email Challenge Options (`emailChallenge`)
|
|
2310
|
+
|
|
2311
|
+
Read only with `emailChallengeRepositoryInstance`. See [Email Challenges](#email-challenges).
|
|
2312
|
+
|
|
2313
|
+
| Option | Type | Default | Description |
|
|
2314
|
+
|--------|------|---------|-------------|
|
|
2315
|
+
| `ttlMinutes` | `number` | `15` | How long a code lasts, 1 to 60. `issue` can override it per call |
|
|
2316
|
+
| `issueLimits.cooldownSeconds` | `number` | `60` | Seconds between two codes for one address and purpose. `0` turns it off |
|
|
2317
|
+
| `issueLimits.perHour` | `number` | `5` | Codes an hour for one address and purpose. At least 1 |
|
|
2318
|
+
| `issueLimits.perIpPerHour` | `number` | `30` | Codes an hour from one IP, across addresses. `0` turns it off |
|
|
2319
|
+
|
|
2320
|
+
### Guest Session Options (`guestSession`)
|
|
2321
|
+
|
|
2322
|
+
Read only with `guestSessionRepositoryInstance`. See [Guest Sessions](#guest-sessions).
|
|
2323
|
+
|
|
2324
|
+
| Option | Type | Default | Description |
|
|
2325
|
+
|--------|------|---------|-------------|
|
|
2326
|
+
| `maxGrantsPerSession` | `number` | `10` | Standing grants one session may hold, 1 to 100. `1` gives one grant per session |
|
|
2327
|
+
| `maxLifetimeDays` | `number` | `30` | The furthest ahead a grant's `expiresAt` may be, 1 to 365 |
|
|
2328
|
+
|
|
1974
2329
|
### Optional Instance Options
|
|
1975
2330
|
|
|
1976
2331
|
| Option | Interface | Fallback |
|
|
@@ -1982,6 +2337,8 @@ reported at boot as ignored. With it, `rp` is required.
|
|
|
1982
2337
|
| `bruteForceRepositoryInstance` | `IBruteForceRepository` | `NoOpBruteForceRepository` |
|
|
1983
2338
|
| `biometricRepositoryInstance` | `IBiometricRepository` | none: biometrics disabled |
|
|
1984
2339
|
| `passkeyRepositoryInstance` | `IPasskeyRepository` | none: passkeys disabled |
|
|
2340
|
+
| `emailChallengeRepositoryInstance` | `IEmailChallengeRepository` | none: email challenges unavailable |
|
|
2341
|
+
| `guestSessionRepositoryInstance` | `IGuestSessionRepository` | none: guest sessions disabled |
|
|
1985
2342
|
| `authLoggerInstance` | `IAuthLogger` | `ConsoleAuthLogger` |
|
|
1986
2343
|
| `tenantRepositoryInstance` | `ITenantRepository` | `null` |
|
|
1987
2344
|
| `tenantExtractorInstance` | `ITenantExtractor` | `null` |
|
|
@@ -2004,6 +2361,8 @@ reported at boot as ignored. With it, `rp` is required.
|
|
|
2004
2361
|
| `@RequirePermissions('p1', 'p2')` | Method/Class | Require specific permissions |
|
|
2005
2362
|
| `@RequireScopes('s1', 's2')` | Method/Class | Require API key scopes, enforced by `ScopeGuard`. People are unaffected. See [API Key Authentication](#api-key-authentication) |
|
|
2006
2363
|
| `@CurrentUser()` | Parameter | Inject authenticated user |
|
|
2364
|
+
| `@AllowGuestSession('kind')` | **Method only** | Let a guest session with a standing grant of that kind reach the handler. See [Guest Sessions](#guest-sessions) |
|
|
2365
|
+
| `@CurrentGuest()` | Parameter | Inject the admitted guest, or `undefined` for a user |
|
|
2007
2366
|
| `@CurrentTenant()` | Parameter | Inject resolved tenant context |
|
|
2008
2367
|
| `@CurrentRealm()` | Parameter | Inject resolved realm from request context |
|
|
2009
2368
|
| `@ResourceScope('resourceType', 'argName')` | Method | Resource-level permission scoping |
|
|
@@ -2012,7 +2371,8 @@ reported at boot as ignored. With it, `rp` is required.
|
|
|
2012
2371
|
|
|
2013
2372
|
| Guard | Description |
|
|
2014
2373
|
|-------|-------------|
|
|
2015
|
-
| `createAuthGuard(strategies, options)` | Factory for multi-strategy auth guards |
|
|
2374
|
+
| `createAuthGuard(strategies, options)` | Factory for multi-strategy auth guards. Also admits guests on handlers marked `@AllowGuestSession()`, after trying the user strategies |
|
|
2375
|
+
| `GuestSessionGuard` | Guests only, on handlers marked `@AllowGuestSession()` |
|
|
2016
2376
|
| `CsrfGuard` | CSRF header validation for cookie auth |
|
|
2017
2377
|
| `TenantGuard` | Multi-tenant context resolution |
|
|
2018
2378
|
| `PermissionGuard` | Permission checking against tenant context |
|
|
@@ -2110,6 +2470,8 @@ export class CustomGuard implements CanActivate {
|
|
|
2110
2470
|
- **`__Host-` cookie prefix** support for enhanced cookie security
|
|
2111
2471
|
- **Separate HMAC secrets** for refresh tokens, verification codes, and OAuth state
|
|
2112
2472
|
- **Realm-based identity isolation** with JWT realm claim validation, realm-scoped rate limiting and lockout, and auth flows that refuse a request without a realm
|
|
2473
|
+
- **Guest sessions for non-users** -- an opaque token stored as a keyed hash, never a JWT, kept off `request.user`, refused by every route that has not opted in by name, with a hard end per grant, revocation effective on the next request, and the token replaced whenever a grant is added (see [Guest Sessions](#guest-sessions))
|
|
2474
|
+
- **Email challenges for non-users** -- one-time codes whose address, purpose and code are stored only as keyed hashes, bound to the address, purpose and realm they were issued for, with attempts and single use enforced by atomic writes in your store (see [Email Challenges](#email-challenges))
|
|
2113
2475
|
- **Pinned JWT algorithms** -- the package's own `jwt` strategy verifies `HS256` only, and `createExternalJwtStrategy` requires an explicit `algorithms` list from one family (see [External JWTs](#external-jwts))
|
|
2114
2476
|
- **API keys with scopes and expiry**, refused hard when they carry your prefix and match no key, so no other strategy accepts them
|
|
2115
2477
|
- **No raw personal data in package logs** — log lines use the user ID or a salted hash (`emailHash=`, `ipHash=`, under `AUTH_IP_HASH_SALT` or `jwtSecret`), phone numbers keep only their last two digits, and the default `ConsoleAuthLogger` redacts security-event metadata. A custom `authLoggerInstance` receives the raw fields; pass them through `redactSecurityEventMetadata` to store them redacted.
|
package/dist/auth.module.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ import { IVerificationRepository } from './interfaces/verification-repository.in
|
|
|
10
10
|
import { IBruteForceRepository } from './interfaces/brute-force-repository.interface';
|
|
11
11
|
import { IBiometricRepository } from './interfaces/biometric-repository.interface';
|
|
12
12
|
import { IPasskeyRepository } from './interfaces/passkey-repository.interface';
|
|
13
|
+
import { IEmailChallengeRepository } from './interfaces/email-challenge-repository.interface';
|
|
14
|
+
import { IGuestSessionRepository } from './interfaces/guest-session-repository.interface';
|
|
13
15
|
import { PasskeyRelyingPartyConfig } from './utils/passkey-rp';
|
|
14
16
|
import { IAuthLogger } from './interfaces/auth-logger.interface';
|
|
15
17
|
import { ITenantRepository } from './interfaces/tenant-repository.interface';
|
|
@@ -35,6 +37,8 @@ export interface AuthModuleOptions {
|
|
|
35
37
|
bruteForceRepositoryInstance?: IBruteForceRepository;
|
|
36
38
|
biometricRepositoryInstance?: IBiometricRepository;
|
|
37
39
|
passkeyRepositoryInstance?: IPasskeyRepository;
|
|
40
|
+
emailChallengeRepositoryInstance?: IEmailChallengeRepository;
|
|
41
|
+
guestSessionRepositoryInstance?: IGuestSessionRepository;
|
|
38
42
|
authLoggerInstance?: IAuthLogger;
|
|
39
43
|
refreshTokenSecret?: string;
|
|
40
44
|
verificationCodeSecret?: string;
|
|
@@ -148,6 +152,18 @@ export interface AuthModuleOptions {
|
|
|
148
152
|
removed?: boolean;
|
|
149
153
|
};
|
|
150
154
|
};
|
|
155
|
+
emailChallenge?: {
|
|
156
|
+
ttlMinutes?: number;
|
|
157
|
+
issueLimits?: {
|
|
158
|
+
cooldownSeconds?: number;
|
|
159
|
+
perHour?: number;
|
|
160
|
+
perIpPerHour?: number;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
guestSession?: {
|
|
164
|
+
maxGrantsPerSession?: number;
|
|
165
|
+
maxLifetimeDays?: number;
|
|
166
|
+
};
|
|
151
167
|
stepUp?: {
|
|
152
168
|
ttlSeconds?: number;
|
|
153
169
|
issueOnLogin?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../src/auth.module.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,kBAAkB,EAElB,UAAU,EAGX,MAAM,gBAAgB,CAAC;AAIxB,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../src/auth.module.ts"],"names":[],"mappings":"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"}
|