@ambushsoftworks/nestjs-auth-graphql 0.11.0 → 0.14.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 +300 -0
- package/README.md +411 -3
- package/dist/auth.module.d.ts +15 -1
- package/dist/auth.module.d.ts.map +1 -1
- package/dist/auth.module.js +114 -16
- 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/require-scopes.decorator.d.ts +2 -0
- package/dist/decorators/require-scopes.decorator.d.ts.map +1 -0
- package/dist/decorators/require-scopes.decorator.js +8 -0
- package/dist/decorators/require-scopes.decorator.js.map +1 -0
- package/dist/guards/csrf.guard.d.ts +2 -0
- package/dist/guards/csrf.guard.d.ts.map +1 -1
- package/dist/guards/csrf.guard.js +9 -2
- package/dist/guards/csrf.guard.js.map +1 -1
- package/dist/guards/scope.guard.d.ts +8 -0
- package/dist/guards/scope.guard.d.ts.map +1 -0
- package/dist/guards/scope.guard.js +53 -0
- package/dist/guards/scope.guard.js.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/interfaces/api-key-repository.interface.d.ts +3 -0
- package/dist/interfaces/api-key-repository.interface.d.ts.map +1 -1
- package/dist/interfaces/biometric-repository.interface.d.ts +29 -17
- package/dist/interfaces/biometric-repository.interface.d.ts.map +1 -1
- package/dist/interfaces/biometric-verifier.interface.d.ts +10 -0
- package/dist/interfaces/biometric-verifier.interface.d.ts.map +1 -0
- package/dist/interfaces/{magic-link-repository.interface.js → biometric-verifier.interface.js} +1 -1
- package/dist/interfaces/biometric-verifier.interface.js.map +1 -0
- package/dist/resolvers/base-auth.resolver.d.ts.map +1 -1
- package/dist/resolvers/base-auth.resolver.js +0 -15
- package/dist/resolvers/base-auth.resolver.js.map +1 -1
- package/dist/services/auth.service.d.ts.map +1 -1
- package/dist/services/auth.service.js +10 -3
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/biometric-auth.service.d.ts +47 -17
- package/dist/services/biometric-auth.service.d.ts.map +1 -1
- package/dist/services/biometric-auth.service.js +109 -68
- package/dist/services/biometric-auth.service.js.map +1 -1
- package/dist/services/biometric-challenge.service.d.ts +27 -0
- package/dist/services/biometric-challenge.service.d.ts.map +1 -0
- package/dist/services/biometric-challenge.service.js +116 -0
- package/dist/services/biometric-challenge.service.js.map +1 -0
- package/dist/services/biometric-verification.service.d.ts.map +1 -1
- package/dist/services/biometric-verification.service.js +4 -0
- package/dist/services/biometric-verification.service.js.map +1 -1
- package/dist/services/verification.service.d.ts +2 -1
- package/dist/services/verification.service.d.ts.map +1 -1
- package/dist/services/verification.service.js +38 -8
- package/dist/services/verification.service.js.map +1 -1
- package/dist/strategies/api-key.strategy.d.ts +6 -1
- package/dist/strategies/api-key.strategy.d.ts.map +1 -1
- package/dist/strategies/api-key.strategy.js +40 -6
- package/dist/strategies/api-key.strategy.js.map +1 -1
- package/dist/strategies/external-jwt.strategy.d.ts +19 -0
- package/dist/strategies/external-jwt.strategy.d.ts.map +1 -0
- package/dist/strategies/external-jwt.strategy.js +124 -0
- package/dist/strategies/external-jwt.strategy.js.map +1 -0
- package/dist/strategies/jwt.strategy.d.ts.map +1 -1
- package/dist/strategies/jwt.strategy.js +1 -0
- package/dist/strategies/jwt.strategy.js.map +1 -1
- package/dist/test-utils/mock-repositories.d.ts.map +1 -1
- package/dist/test-utils/mock-repositories.js +8 -7
- package/dist/test-utils/mock-repositories.js.map +1 -1
- package/dist/utils/provider-helpers.d.ts +6 -0
- package/dist/utils/provider-helpers.d.ts.map +1 -1
- package/dist/utils/provider-helpers.js +4 -0
- package/dist/utils/provider-helpers.js.map +1 -1
- package/dist/utils/verification-base-url.d.ts +2 -0
- package/dist/utils/verification-base-url.d.ts.map +1 -0
- package/dist/utils/verification-base-url.js +19 -0
- package/dist/utils/verification-base-url.js.map +1 -0
- package/dist/verifiers/es256-device-key.verifier.d.ts +14 -0
- package/dist/verifiers/es256-device-key.verifier.d.ts.map +1 -0
- package/dist/verifiers/es256-device-key.verifier.js +42 -0
- package/dist/verifiers/es256-device-key.verifier.js.map +1 -0
- package/package.json +8 -2
- package/dist/interfaces/magic-link-repository.interface.d.ts +0 -6
- package/dist/interfaces/magic-link-repository.interface.d.ts.map +0 -1
- package/dist/interfaces/magic-link-repository.interface.js.map +0 -1
- package/dist/interfaces/password-reset-strategy.interface.d.ts +0 -7
- package/dist/interfaces/password-reset-strategy.interface.d.ts.map +0 -1
- package/dist/interfaces/password-reset-strategy.interface.js +0 -3
- package/dist/interfaces/password-reset-strategy.interface.js.map +0 -1
- package/dist/repositories/noop-biometric.repository.d.ts +0 -26
- package/dist/repositories/noop-biometric.repository.d.ts.map +0 -1
- package/dist/repositories/noop-biometric.repository.js +0 -56
- package/dist/repositories/noop-biometric.repository.js.map +0 -1
- package/dist/repositories/noop-magic-link.repository.d.ts +0 -9
- package/dist/repositories/noop-magic-link.repository.d.ts.map +0 -1
- package/dist/repositories/noop-magic-link.repository.js +0 -37
- package/dist/repositories/noop-magic-link.repository.js.map +0 -1
- package/dist/strategies/magic-link.strategy.d.ts +0 -16
- package/dist/strategies/magic-link.strategy.d.ts.map +0 -1
- package/dist/strategies/magic-link.strategy.js +0 -80
- package/dist/strategies/magic-link.strategy.js.map +0 -1
- package/dist/strategies/verification-code.strategy.d.ts +0 -11
- package/dist/strategies/verification-code.strategy.d.ts.map +0 -1
- package/dist/strategies/verification-code.strategy.js +0 -44
- package/dist/strategies/verification-code.strategy.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,10 +13,14 @@ Production-grade authentication module for NestJS GraphQL + REST APIs. JWT, cook
|
|
|
13
13
|
- [Multi-Tenancy](#multi-tenancy)
|
|
14
14
|
- [Realm-Based Identity Isolation](#realm-based-identity-isolation)
|
|
15
15
|
- [API Key Authentication](#api-key-authentication)
|
|
16
|
+
- [External JWTs](#external-jwts)
|
|
16
17
|
- [Email System](#email-system)
|
|
17
18
|
- [Verification Modes](#verification-modes)
|
|
19
|
+
- [Phone \& SMS Verification](#phone--sms-verification)
|
|
18
20
|
- [OAuth](#oauth)
|
|
21
|
+
- [Biometric Authentication](#biometric-authentication)
|
|
19
22
|
- [Brute Force Protection](#brute-force-protection)
|
|
23
|
+
- [Password Reset](#password-reset)
|
|
20
24
|
- [Password Policy](#password-policy)
|
|
21
25
|
- [Lifecycle Hooks](#lifecycle-hooks)
|
|
22
26
|
- [Account Status](#account-status)
|
|
@@ -33,12 +37,17 @@ Production-grade authentication module for NestJS GraphQL + REST APIs. JWT, cook
|
|
|
33
37
|
- [CSRF Options](#csrf-options-csrf)
|
|
34
38
|
- [Composable Email](#composable-email-email)
|
|
35
39
|
- [Verification Options](#verification-options-verification)
|
|
40
|
+
- [Biometric Options (`biometric`)](#biometric-options-biometric)
|
|
41
|
+
- [SendGrid Options (`sendgrid`)](#sendgrid-options-sendgrid)
|
|
42
|
+
- [Twilio Options (`twilio`)](#twilio-options-twilio)
|
|
36
43
|
- [Optional Instance Options](#optional-instance-options)
|
|
37
44
|
- [Decorators](#decorators)
|
|
38
45
|
- [Guards](#guards)
|
|
39
46
|
- [GraphQL Subscriptions](#graphql-subscriptions)
|
|
40
47
|
- [Utilities](#utilities)
|
|
41
48
|
- [Security Features](#security-features)
|
|
49
|
+
- [Upgrading from 0.9.x](#upgrading-from-09x)
|
|
50
|
+
- [Migrating to v0.12.0](#migrating-to-v0120)
|
|
42
51
|
- [Migrating to v0.11.0](#migrating-to-v0110)
|
|
43
52
|
- [Migrating to v0.10.0](#migrating-to-v0100)
|
|
44
53
|
- [Migrating to v0.9.0](#migrating-to-v090)
|
|
@@ -559,6 +568,73 @@ The `ApiKeyStrategy` hashes the bearer token with SHA-256 and calls `findByKeyHa
|
|
|
559
568
|
|
|
560
569
|
Before 0.10.0 the API key strategy rejected an unknown bearer token itself, which stopped the chain: `['api-key', 'jwt']` rejected every JWT.
|
|
561
570
|
|
|
571
|
+
#### Key options
|
|
572
|
+
|
|
573
|
+
| Option | Example | Effect |
|
|
574
|
+
|--------|---------|--------|
|
|
575
|
+
| `apiKey.headerName` | `'X-API-Key'` | A header carrying the raw key, read before `Authorization: Bearer`, which keeps working. `CsrfGuard` treats a request carrying it as credentialed, as it already does for `Authorization` |
|
|
576
|
+
| `apiKey.prefix` | `'ait_'` | The prefix every key you issue starts with. See below |
|
|
577
|
+
|
|
578
|
+
Both are read only with `apiKeyRepositoryInstance`, and `apiKey.prefix` without it warns at boot. `headerName` must be a header name other than `Authorization`, and `prefix` must not be empty -- an empty one matches every token, so every unknown bearer token, JWTs included, would be refused. Boot fails on either.
|
|
579
|
+
|
|
580
|
+
**A prefix makes an unknown key unambiguous.** Without one, the strategy cannot tell a mistyped key from a JWT:
|
|
581
|
+
|
|
582
|
+
| Token | Without `prefix` | With `prefix` |
|
|
583
|
+
|-------|------------------|---------------|
|
|
584
|
+
| Does not start with the prefix (a JWT, say) | Looked up, then passed to the next strategy | Passed to the next strategy, with no lookup |
|
|
585
|
+
| Starts with it, matches no key | Passed to the next strategy; a single-strategy guard answers a generic 401 | `401 Invalid API key` -- it cannot be a JWT, so no other strategy tries it |
|
|
586
|
+
| Matches a key | Authenticated, unless the key is inactive or expired | The same |
|
|
587
|
+
|
|
588
|
+
#### Expiry, scopes and last use
|
|
589
|
+
|
|
590
|
+
`IApiKeyAccount` carries optional `scopes?: string[]` and `expiresAt?: Date | null`, and `IApiKeyRepository` may implement `touchLastUsed?(id)`. Existing repositories keep working.
|
|
591
|
+
|
|
592
|
+
- **Expiry.** A key whose `expiresAt` has passed is refused with `401 API key has expired`, and no other strategy tries it. A key without one never expires.
|
|
593
|
+
- **Scopes.** Mark the operation and register `ScopeGuard` after your authentication guard:
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
@Get('tickets')
|
|
597
|
+
@UseGuards(createAuthGuard(['api-key', 'jwt']), ScopeGuard)
|
|
598
|
+
@RequireScopes('tickets:read')
|
|
599
|
+
listTickets() { /* ... */ }
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
A key must hold every listed scope. One that does not gets 403 with `{ message: 'Insufficient scope', code: 'INSUFFICIENT_SCOPE', requiredScopes, statusCode: 403 }`, naming what the operation requires. A key with no `scopes` holds none. **Signed-in people pass**: scopes restrict keys, while a person's access is `PermissionGuard`'s job. With no authenticated principal at all, `ScopeGuard` answers 401 -- which is why it goes after the auth guard.
|
|
603
|
+
- **Last use.** `touchLastUsed(id)` is called once per successful match, and only then -- never for a key that is refused. It is not awaited: a failure is logged and never fails the request.
|
|
604
|
+
|
|
605
|
+
### External JWTs
|
|
606
|
+
|
|
607
|
+
`createExternalJwtStrategy(name, options)` verifies tokens another system signs -- each app's backend signing for its own users, say. It registers a separate Passport strategy under `name`, with no realm check and no user lookup, so the package's own `jwt` strategy is untouched.
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
export const CustomerJwtStrategy = createExternalJwtStrategy('customer-jwt', {
|
|
611
|
+
inject: [AppSecretsService], // optional; pass a plain options object instead
|
|
612
|
+
useFactory: (secrets: AppSecretsService) => ({
|
|
613
|
+
algorithms: ['HS256'], // required
|
|
614
|
+
audience: 'ambush-desk',
|
|
615
|
+
secretProvider: (kid) => secrets.findSecret(kid),
|
|
616
|
+
mapClaims: (claims) => ({ id: claims.sub, app: claims.app, segments: claims.segments }),
|
|
617
|
+
}),
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// providers: [CustomerJwtStrategy]
|
|
621
|
+
// @UseGuards(createAuthGuard(['customer-jwt']))
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
| Option | Default | Description |
|
|
625
|
+
|--------|---------|-------------|
|
|
626
|
+
| `secretProvider(kid, request)` | -- | The key that verifies a token: a shared secret for `HS*`, a PEM public key for `RS*`, `PS*` and `ES*`. May be async. `null` rejects the token; so does a throw, which is logged |
|
|
627
|
+
| `algorithms` | -- | **Required.** The algorithms the issuer signs with |
|
|
628
|
+
| `issuer`, `audience` | -- | Required `iss` / `aud`, when set |
|
|
629
|
+
| `clockToleranceSec` | `0` | Clock skew allowed on `exp` and `nbf` |
|
|
630
|
+
| `maxTokenBytes` | `8192` | A longer token is refused before anything parses it |
|
|
631
|
+
| `mapClaims(payload)` | the payload | Builds `request.user`, arrays and custom claims intact. `null`, `undefined` or a throw rejects the token |
|
|
632
|
+
| `jwtFromRequest(request)` | `Authorization: Bearer` | Where the token comes from |
|
|
633
|
+
|
|
634
|
+
**Algorithms are pinned, one family at a time.** Boot fails with `InvalidAuthConfigException` for missing or empty `algorithms`, for `none`, for an algorithm jsonwebtoken does not know, and for HMAC (`HS*`) mixed with asymmetric algorithms -- the mix that lets a public key be used as an HMAC secret. `'jwt'` and `'api-key'` are refused as names, since Passport would replace the package's own strategy.
|
|
635
|
+
|
|
636
|
+
**Every rejection fails softly**, so the strategy composes: `createAuthGuard(['customer-jwt', 'jwt'])` accepts either kind of token, in either order, and answers 401 when both fail.
|
|
637
|
+
|
|
562
638
|
### Email System
|
|
563
639
|
|
|
564
640
|
The email system uses a composable architecture: a **sender** (transport) and a **template renderer** (HTML generation).
|
|
@@ -688,6 +764,40 @@ variable (deprecated — logged once). With neither, code-mode emails carry no
|
|
|
688
764
|
link and nothing is logged: the code is in the body, so no link is needed. The
|
|
689
765
|
code itself is never placed in a query string in either mode.
|
|
690
766
|
|
|
767
|
+
#### One API, several brands: `baseUrl` per realm
|
|
768
|
+
|
|
769
|
+
With [realms](#realm-based-identity-isolation) enabled, one deployment serves
|
|
770
|
+
several frontends and a link must open on the site the user actually came from.
|
|
771
|
+
`baseUrl` accepts a resolver as well as a string; it is called with the realm of
|
|
772
|
+
the request the credential is being issued for:
|
|
773
|
+
|
|
774
|
+
```typescript
|
|
775
|
+
const SITES: Record<string, string> = {
|
|
776
|
+
'site-a': 'https://a.example.com',
|
|
777
|
+
'site-b': 'https://b.example.com',
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
verification: {
|
|
781
|
+
baseUrl: (realm?: string) => SITES[realm ?? ''] ?? 'https://www.example.com',
|
|
782
|
+
},
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
It applies to both modes — the token link and the code page link — because both
|
|
786
|
+
are built from the same resolved base. It is called once per link, so resolve
|
|
787
|
+
from a map rather than a query, and it must be synchronous.
|
|
788
|
+
|
|
789
|
+
**Boot validation is weaker for a resolver, by necessity.** A string is checked
|
|
790
|
+
in full at startup. A resolver is only called once, with `undefined`, so the
|
|
791
|
+
realm-less case is checked and a per-realm return value is not — there is no
|
|
792
|
+
request at startup to supply a realm. What it returns for a specific realm is
|
|
793
|
+
checked when the link is built, and the two modes differ exactly as they do
|
|
794
|
+
elsewhere: token mode throws, because the link carries the only copy of the
|
|
795
|
+
credential; code mode logs a warning and sends the email without a link,
|
|
796
|
+
because the code is in the body.
|
|
797
|
+
|
|
798
|
+
Returning a value for `realm === undefined` is required. A request exempted by
|
|
799
|
+
`realm.skip`, or a single-realm deployment, has no realm.
|
|
800
|
+
|
|
691
801
|
**Custom `IEmailService` implementations.** Both send methods receive the
|
|
692
802
|
credential and its URL, and the mode determines which one is actionable:
|
|
693
803
|
|
|
@@ -702,6 +812,45 @@ link. `expiresInMinutes` reflects the mode's real expiry, so render it rather
|
|
|
702
812
|
than hardcoding a duration. Both trailing parameters are optional, so existing
|
|
703
813
|
implementations continue to compile.
|
|
704
814
|
|
|
815
|
+
### Phone & SMS Verification
|
|
816
|
+
|
|
817
|
+
An authenticated user adds a phone number, receives a 6-digit code by SMS, and
|
|
818
|
+
confirms it. All five methods take the current user, so they sit behind your
|
|
819
|
+
authentication guard — this is account management, not a sign-in path.
|
|
820
|
+
|
|
821
|
+
| Method | Purpose |
|
|
822
|
+
|---|---|
|
|
823
|
+
| `performSendPhoneVerification(input, user)` | Store the number and send a code |
|
|
824
|
+
| `performVerifyPhone(input, user)` | Confirm the code, mark the number verified |
|
|
825
|
+
| `performResendPhoneVerification(phoneNumber, user)` | Send again, subject to the 60-second cooldown |
|
|
826
|
+
| `performRemovePhoneNumber(user)` | Clear the number and its verified state |
|
|
827
|
+
| `performPhoneVerificationStatus(user)` | Whether a number is present and verified |
|
|
828
|
+
|
|
829
|
+
Provide `smsServiceInstance` and `verificationRepositoryInstance`. `ISmsService`
|
|
830
|
+
has a single method, so the seam is small:
|
|
831
|
+
|
|
832
|
+
```typescript
|
|
833
|
+
export class TwilioSmsService implements ISmsService {
|
|
834
|
+
async sendVerificationSms(phoneNumber: string, code: string): Promise<void> {
|
|
835
|
+
await this.client.messages.create({ to: phoneNumber, body: `Your code is ${code}` });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
Two things worth knowing:
|
|
841
|
+
|
|
842
|
+
- **SMS is code-only.** `features.verificationMode: 'token'` governs email
|
|
843
|
+
verification and password reset. A phone always receives a 6-digit code,
|
|
844
|
+
because a link is not usable from a text message in the same way — so
|
|
845
|
+
`verification.baseUrl` is irrelevant here.
|
|
846
|
+
- **Omitting `smsServiceInstance` does not fail loudly.** The package substitutes
|
|
847
|
+
a no-op that logs, so codes are generated and stored but never delivered.
|
|
848
|
+
Verification then only succeeds for someone reading your application logs.
|
|
849
|
+
|
|
850
|
+
The number's realm is scoped like any other identity field: with realms enabled,
|
|
851
|
+
`findByPhoneNumber` receives the realm, so the same number can exist once per
|
|
852
|
+
realm.
|
|
853
|
+
|
|
705
854
|
### OAuth
|
|
706
855
|
|
|
707
856
|
Google and Facebook OAuth with encrypted token storage (AES-256-GCM).
|
|
@@ -755,6 +904,110 @@ It sits beside `useFactory` because Nest fixes a module's controllers before asy
|
|
|
755
904
|
|
|
756
905
|
With realms enabled, a request without a realm gets `400 Realm required` first. Before 0.10.0 the lockout, inactive-account, rate-limit and unconfigured-provider cases answered 500.
|
|
757
906
|
|
|
907
|
+
### Biometric Authentication
|
|
908
|
+
|
|
909
|
+
A device holds an ECDSA P-256 keypair, gated behind the OS biometric prompt. The
|
|
910
|
+
server stores the public key and verifies a signature over a challenge it issued.
|
|
911
|
+
|
|
912
|
+
```typescript
|
|
913
|
+
biometricRepositoryInstance: myBiometricRepo, // enables the capability
|
|
914
|
+
biometric: {
|
|
915
|
+
challengeExpirySeconds: 60, // default
|
|
916
|
+
challengeRateLimit: { maxAttempts: 5, windowMs: 60_000 }, // needs rateLimiterInstance
|
|
917
|
+
},
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
#### ⚠ What a successful authentication actually proves
|
|
921
|
+
|
|
922
|
+
**It proves the client still holds the private key. It does not prove a biometric
|
|
923
|
+
check happened.** Nothing in an ECDSA signature attests to that, and the server
|
|
924
|
+
cannot distinguish a key held in a hardware enclave from one generated in
|
|
925
|
+
software. Whether a fingerprint was scanned — or a PIN accepted instead, which
|
|
926
|
+
most mobile biometric APIs allow by default — is the client's word.
|
|
927
|
+
|
|
928
|
+
Treat this as *possession of a device-bound key that the client promises to
|
|
929
|
+
gate*. It is a good second factor and a good convenience login. Before making it
|
|
930
|
+
the sole factor for something sensitive, be clear that you are trusting the app,
|
|
931
|
+
not the biometric. WebAuthn's `userVerification` flag is what actually attests to
|
|
932
|
+
user verification, and is not this.
|
|
933
|
+
|
|
934
|
+
#### The flow
|
|
935
|
+
|
|
936
|
+
```typescript
|
|
937
|
+
// 1. Enrol, authenticated. The package generates the credential id — keep it
|
|
938
|
+
// on the device alongside the private key.
|
|
939
|
+
const { credentialId } = await biometricAuth.enrolCredential({
|
|
940
|
+
userId: user.id,
|
|
941
|
+
publicKey, // PEM, SPKI
|
|
942
|
+
deviceName: 'iPhone 15',
|
|
943
|
+
metadata: { yourDeviceString: '...' }, // optional, opaque to the package
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
// 2. Request a challenge. No user id — the caller is usually signed out.
|
|
947
|
+
const challenge = await biometricAuth.requestChallenge(credentialId, {
|
|
948
|
+
ipAddress: req.ip,
|
|
949
|
+
});
|
|
950
|
+
if (!challenge) { /* rate limited */ }
|
|
951
|
+
|
|
952
|
+
// 3. The client signs `challenge.challenge` (base64) with the private key.
|
|
953
|
+
|
|
954
|
+
// 4. Verify and receive a session.
|
|
955
|
+
const session = await biometricAuth.authenticateWithBiometric(
|
|
956
|
+
{ challengeId: challenge.challengeId, credentialId, signature },
|
|
957
|
+
{ res, realm, ipAddress: req.ip },
|
|
958
|
+
);
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
Also `listCredentials(userId)` for a device list and `removeCredential(userId,
|
|
962
|
+
credentialId)` to deactivate one.
|
|
963
|
+
|
|
964
|
+
#### What the package guarantees
|
|
965
|
+
|
|
966
|
+
| | |
|
|
967
|
+
|---|---|
|
|
968
|
+
| Challenge | 32 random bytes, single use, 60s default |
|
|
969
|
+
| Replay | A challenge is consumed **atomically** before anything is checked, so a retry loses the race — and a failed attempt still burns it |
|
|
970
|
+
| Cross-credential | A challenge issued for one credential cannot be answered with another's key |
|
|
971
|
+
| Algorithm | Pinned **per credential**. A credential enrolled as ES256 is only verified as ES256; no verifier for its algorithm means refusal, not a fallback |
|
|
972
|
+
| Account status | The session comes from the same sink as every other login, so a suspended or soft-deleted user is refused **after** a valid signature |
|
|
973
|
+
| Enumeration | Every biometric failure returns the same 401, and a challenge is issued even for a credential that does not exist |
|
|
974
|
+
| Rate limiting | Per credential, and per IP so varying the credential id cannot evade it |
|
|
975
|
+
|
|
976
|
+
#### Implementing `IBiometricRepository`
|
|
977
|
+
|
|
978
|
+
One method has a contract you cannot satisfy with a read followed by a write:
|
|
979
|
+
|
|
980
|
+
```typescript
|
|
981
|
+
async consumeChallenge(challengeId: string) {
|
|
982
|
+
// Atomic: mark used and return it, only if it was unused and unexpired.
|
|
983
|
+
const { count } = await prisma.biometricChallenge.updateMany({
|
|
984
|
+
where: { id: challengeId, used: false, expiresAt: { gt: new Date() } },
|
|
985
|
+
data: { used: true, usedAt: new Date() },
|
|
986
|
+
});
|
|
987
|
+
if (count !== 1) return null; // someone else claimed it, or it expired
|
|
988
|
+
return this.load(challengeId);
|
|
989
|
+
}
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
Read-then-write lets two concurrent requests with one challenge both succeed,
|
|
993
|
+
which is the replay single use exists to prevent.
|
|
994
|
+
|
|
995
|
+
Two more things your schema needs to know:
|
|
996
|
+
|
|
997
|
+
- **`credentialId` on a challenge is not a foreign key.** A challenge is stored
|
|
998
|
+
for credential ids that do not exist, so that the signed-out request cannot be
|
|
999
|
+
used to discover which credentials are enrolled.
|
|
1000
|
+
- **No uniqueness on `deviceName` or any device string.** It is a display label,
|
|
1001
|
+
never matched on. Key rotation is enrol-new then deactivate-old.
|
|
1002
|
+
|
|
1003
|
+
`getCredential` must return deactivated credentials rather than hiding them — the
|
|
1004
|
+
package decides, so that a deactivated credential and an unknown one produce the
|
|
1005
|
+
same answer.
|
|
1006
|
+
|
|
1007
|
+
**There is no no-op fallback.** Omit `biometricRepositoryInstance` and the
|
|
1008
|
+
biometric services are not registered at all, rather than silently accepting
|
|
1009
|
+
enrolments that can never authenticate.
|
|
1010
|
+
|
|
758
1011
|
### Brute Force Protection
|
|
759
1012
|
|
|
760
1013
|
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.
|
|
@@ -859,6 +1112,78 @@ async changePassword(
|
|
|
859
1112
|
|
|
860
1113
|
`issueAuthSession(userId, res?, realm?)` mints a new access + refresh pair, persists the refresh token, and (when `features.cookieAuth` is enabled and `res` is supplied) sets the auth cookies on the response. The caller is responsible for verifying the user's identity — `issueAuthSession` takes a `userId` and trusts it. Pair it only with flows that have already authenticated the user (`changePassword`, MFA enrollment completion, admin-impersonation reissue, etc.). Emits `SecurityEvent.SESSION_ISSUED`.
|
|
861
1114
|
|
|
1115
|
+
### Password Reset
|
|
1116
|
+
|
|
1117
|
+
A reset is two calls: `performRequestPasswordReset` sends a 6-digit code, and
|
|
1118
|
+
`performResetPassword` redeems it. `performChangePassword` is the separate,
|
|
1119
|
+
authenticated path for a signed-in user who knows their current password.
|
|
1120
|
+
|
|
1121
|
+
```typescript
|
|
1122
|
+
@Mutation(() => PasswordResetResponse)
|
|
1123
|
+
requestPasswordReset(@Args('input') input: RequestPasswordResetInput, @Context() ctx: any) {
|
|
1124
|
+
return this.performRequestPasswordReset(input, ctx);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
@Mutation(() => PasswordResetResponse)
|
|
1128
|
+
resetPassword(@Args('input') input: ResetPasswordInput, @Context() ctx: any) {
|
|
1129
|
+
return this.performResetPassword(input, ctx);
|
|
1130
|
+
}
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
It needs `verificationRepositoryInstance` to store the code and
|
|
1134
|
+
`emailServiceInstance` to deliver it. Without the first, codes are logged rather
|
|
1135
|
+
than stored and no reset can complete.
|
|
1136
|
+
|
|
1137
|
+
#### What the package enforces for you
|
|
1138
|
+
|
|
1139
|
+
None of this is configurable, and most of it is invisible until it bites:
|
|
1140
|
+
|
|
1141
|
+
| | |
|
|
1142
|
+
|---|---|
|
|
1143
|
+
| Code | 6 digits, HMAC-SHA256 hashed at rest, never logged |
|
|
1144
|
+
| Expiry | 15 minutes |
|
|
1145
|
+
| Attempts | **3**, then the code is destroyed and a new one must be requested |
|
|
1146
|
+
| Per-user cooldown | 60 seconds, tracked on `passwordResetSentAt` |
|
|
1147
|
+
| Per-IP limit | 5 per hour, checked **before** the user lookup |
|
|
1148
|
+
| On success | **every refresh token for that user is revoked** — all devices are signed out |
|
|
1149
|
+
| Comparison | constant-time |
|
|
1150
|
+
|
|
1151
|
+
The IP limit runs before the lookup deliberately: checking it afterwards would
|
|
1152
|
+
make a request for a non-existent address measurably faster than one for a real
|
|
1153
|
+
address, which is the enumeration leak the generic message exists to prevent.
|
|
1154
|
+
|
|
1155
|
+
#### The response never tells you what happened
|
|
1156
|
+
|
|
1157
|
+
`requestPasswordReset` returns the same success payload whether the address does
|
|
1158
|
+
not exist, belongs to a social-login account, or was actually sent a code. That
|
|
1159
|
+
is deliberate — the mutation is reachable unauthenticated, so any distinguishable
|
|
1160
|
+
result is an oracle for whether an address is registered.
|
|
1161
|
+
|
|
1162
|
+
If you need to know, read your `IAuthLogger`. A skip is reported as
|
|
1163
|
+
`PASSWORD_RESET_REQUESTED` with `result: 'SKIPPED_SOCIAL_ONLY'` and the user ID.
|
|
1164
|
+
An operator tool that creates accounts and invites by reset should assert on that
|
|
1165
|
+
event, not on the mutation's return value.
|
|
1166
|
+
|
|
1167
|
+
#### Accounts with no password
|
|
1168
|
+
|
|
1169
|
+
An account is refused only if it has an actual social identity — `googleId`,
|
|
1170
|
+
`facebookId` or `appleId`. A password-less account with none of those, which is
|
|
1171
|
+
how an operator-provisioned account starts, receives a code and can redeem it.
|
|
1172
|
+
This is the documented "invite by password reset" flow.
|
|
1173
|
+
|
|
1174
|
+
**This changed in 0.12.0.** Before then the test was `passwordHash == null`, so
|
|
1175
|
+
operator-created accounts were skipped silently and could never sign in. See
|
|
1176
|
+
[Migrating to v0.12.0](#migrating-to-v0120) for the consequence worth deciding
|
|
1177
|
+
on. The predicate is exported as `hasSocialIdentity(user)` if you want the same
|
|
1178
|
+
test in your own code.
|
|
1179
|
+
|
|
1180
|
+
#### Token mode
|
|
1181
|
+
|
|
1182
|
+
With `features.verificationMode: 'token'` the email carries a link rather than a
|
|
1183
|
+
code, and `performResetPassword` takes the token lifted from that link in place
|
|
1184
|
+
of the 6-digit code. Everything above still applies. See
|
|
1185
|
+
[Verification Modes](#verification-modes).
|
|
1186
|
+
|
|
862
1187
|
### Password Policy
|
|
863
1188
|
|
|
864
1189
|
```typescript
|
|
@@ -1020,6 +1345,8 @@ These sit beside `useFactory`, not in the object it returns.
|
|
|
1020
1345
|
| `bruteForce.ipRateLimit` | `{ maxAttempts, windowMs }` | -- | IP rate-limit policy for `checkIpRateLimit`. When set, `rateLimiterInstance` MUST also be provided explicitly (boot fails with `InvalidAuthConfigException` otherwise). See [IP Rate Limiting](#ip-rate-limiting). |
|
|
1021
1346
|
| `realm.skip` | `(path, request) => boolean` | -- | Requests that need no realm, such as health checks and webhooks. Read only with `realmExtractorInstance`. See [Realm-Based Identity Isolation](#realm-based-identity-isolation) |
|
|
1022
1347
|
| `refreshGracePeriodSeconds` | `number` | `10` | How long a retried refresh gets the same token pair. `0` turns retries off. See [Refresh Retries](#refresh-retries) |
|
|
1348
|
+
| `apiKey.headerName` | `string` | -- | A header carrying the raw API key, e.g. `'X-API-Key'`, read before `Authorization`. See [API Key Authentication](#api-key-authentication) |
|
|
1349
|
+
| `apiKey.prefix` | `string` | -- | The prefix every API key you issue starts with, e.g. `'ait_'`. Makes an unknown key unambiguous. See [API Key Authentication](#api-key-authentication) |
|
|
1023
1350
|
|
|
1024
1351
|
### Feature Flags (`features`)
|
|
1025
1352
|
|
|
@@ -1094,7 +1421,40 @@ When `email` is provided, it takes precedence over `emailServiceInstance`.
|
|
|
1094
1421
|
|--------|------|---------|-------------|
|
|
1095
1422
|
| `tokenLength` | `number` | `64` | Token length in bytes (token mode) |
|
|
1096
1423
|
| `tokenExpiresInMinutes` | `number` | `60` | Token expiration (token mode) |
|
|
1097
|
-
| `baseUrl` | `string` |
|
|
1424
|
+
| `baseUrl` | `string \| VerificationBaseUrlResolver` | — | Absolute http(s) base URL for verification/reset links, or a resolver called with the request's realm. **Required when `verificationMode` is `'token'`** (validated at boot); optional in code mode |
|
|
1425
|
+
|
|
1426
|
+
### SendGrid Options (`sendgrid`)
|
|
1427
|
+
|
|
1428
|
+
Read only by the bundled `SendGridEmailService`. A custom `IEmailService` ignores
|
|
1429
|
+
this block entirely.
|
|
1430
|
+
|
|
1431
|
+
| Option | Type | Required | Description |
|
|
1432
|
+
|--------|------|----------|-------------|
|
|
1433
|
+
| `apiKey` | `string` | yes | SendGrid API key |
|
|
1434
|
+
| `fromEmail` | `string` | yes | Verified sender address |
|
|
1435
|
+
| `fromName` | `string` | — | Display name on the From header |
|
|
1436
|
+
|
|
1437
|
+
### Twilio Options (`twilio`)
|
|
1438
|
+
|
|
1439
|
+
Read only by the bundled `TwilioSmsService`. A custom `ISmsService` ignores this
|
|
1440
|
+
block entirely.
|
|
1441
|
+
|
|
1442
|
+
| Option | Type | Required | Description |
|
|
1443
|
+
|--------|------|----------|-------------|
|
|
1444
|
+
| `accountSid` | `string` | yes | Twilio account SID |
|
|
1445
|
+
| `authToken` | `string` | yes | Twilio auth token |
|
|
1446
|
+
| `phoneNumber` | `string` | yes | Sending number, E.164 |
|
|
1447
|
+
|
|
1448
|
+
### Biometric Options (`biometric`)
|
|
1449
|
+
|
|
1450
|
+
Read only when `biometricRepositoryInstance` is provided. Passing this block
|
|
1451
|
+
without it is reported at boot, not refused.
|
|
1452
|
+
|
|
1453
|
+
| Option | Type | Default | Description |
|
|
1454
|
+
|--------|------|---------|-------------|
|
|
1455
|
+
| `verifiers` | `IBiometricVerifier[]` | `[new Es256DeviceKeyVerifier()]` | One per credential algorithm. Two claiming the same algorithm is refused at boot — resolution would depend on array order |
|
|
1456
|
+
| `challengeExpirySeconds` | `number` | `60` | Short on purpose: it bounds replay |
|
|
1457
|
+
| `challengeRateLimit` | `{ maxAttempts, windowMs }` | `5 / 60s` | Per credential. **Requires `rateLimiterInstance`** — the same guardrail as `bruteForce.ipRateLimit`, because per-replica counters would make the limit silently looser. A per-IP limit at ten times this bound also applies when you pass `ipAddress` |
|
|
1098
1458
|
|
|
1099
1459
|
### Optional Instance Options
|
|
1100
1460
|
|
|
@@ -1126,6 +1486,7 @@ When `email` is provided, it takes precedence over `emailServiceInstance`.
|
|
|
1126
1486
|
| `@PublicEndpoint()` | Method/Class | Skip authentication + tenant resolution (combines `@Public()` + `@SkipTenant()`) |
|
|
1127
1487
|
| `@SkipTenant()` | Method/Class | Skip tenant resolution |
|
|
1128
1488
|
| `@RequirePermissions('p1', 'p2')` | Method/Class | Require specific permissions |
|
|
1489
|
+
| `@RequireScopes('s1', 's2')` | Method/Class | Require API key scopes, enforced by `ScopeGuard`. People are unaffected. See [API Key Authentication](#api-key-authentication) |
|
|
1129
1490
|
| `@CurrentUser()` | Parameter | Inject authenticated user |
|
|
1130
1491
|
| `@CurrentTenant()` | Parameter | Inject resolved tenant context |
|
|
1131
1492
|
| `@CurrentRealm()` | Parameter | Inject resolved realm from request context |
|
|
@@ -1139,6 +1500,7 @@ When `email` is provided, it takes precedence over `emailServiceInstance`.
|
|
|
1139
1500
|
| `CsrfGuard` | CSRF header validation for cookie auth |
|
|
1140
1501
|
| `TenantGuard` | Multi-tenant context resolution |
|
|
1141
1502
|
| `PermissionGuard` | Permission checking against tenant context |
|
|
1503
|
+
| `ScopeGuard` | `@RequireScopes()` enforcement for API keys; register it after the authentication guard |
|
|
1142
1504
|
| `JwtAuthGuard` | JWT-only guard for GraphQL and HTTP routes (prefer `createAuthGuard` for several strategies or `@Public()`) |
|
|
1143
1505
|
|
|
1144
1506
|
### Recommended Guard Chains
|
|
@@ -1149,8 +1511,9 @@ When realm support is enabled, `RealmMiddleware` runs before all guards (as Nest
|
|
|
1149
1511
|
// JWT only
|
|
1150
1512
|
{ provide: APP_GUARD, useClass: createAuthGuard(['jwt'], { allowPublic: true }) }
|
|
1151
1513
|
|
|
1152
|
-
// JWT + API keys
|
|
1153
|
-
{ provide: APP_GUARD, useClass: createAuthGuard(['jwt', 'api-key'], { allowPublic: true }) }
|
|
1514
|
+
// JWT + API keys, with scopes enforced on the keys
|
|
1515
|
+
{ provide: APP_GUARD, useClass: createAuthGuard(['jwt', 'api-key'], { allowPublic: true }) },
|
|
1516
|
+
{ provide: APP_GUARD, useClass: ScopeGuard },
|
|
1154
1517
|
|
|
1155
1518
|
// Full stack with cookie auth + multi-tenancy
|
|
1156
1519
|
{ provide: APP_GUARD, useClass: CsrfGuard },
|
|
@@ -1231,8 +1594,53 @@ export class CustomGuard implements CanActivate {
|
|
|
1231
1594
|
- **`__Host-` cookie prefix** support for enhanced cookie security
|
|
1232
1595
|
- **Separate HMAC secrets** for refresh tokens, verification codes, and OAuth state
|
|
1233
1596
|
- **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
|
|
1597
|
+
- **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))
|
|
1598
|
+
- **API keys with scopes and expiry**, refused hard when they carry your prefix and match no key, so no other strategy accepts them
|
|
1234
1599
|
- **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.
|
|
1235
1600
|
|
|
1601
|
+
## Upgrading from 0.9.x
|
|
1602
|
+
|
|
1603
|
+
Four releases separate 0.9 from 0.14 and each has its own note below. If you are
|
|
1604
|
+
jumping the whole way, this is the order things will bite, and the short answer
|
|
1605
|
+
for each.
|
|
1606
|
+
|
|
1607
|
+
| Release | What you have to do |
|
|
1608
|
+
|---|---|
|
|
1609
|
+
| **0.10.0** | Usually nothing. If you relied on `['api-key','jwt']` rejecting JWTs, it no longer does — an unknown key now fails softly so the next strategy runs |
|
|
1610
|
+
| **0.11.0** | `getRequestFromContext` can return `undefined`; TypeScript flags every call that assumed otherwise. If you use subscriptions with cookie auth, set `csrf.webSocket` |
|
|
1611
|
+
| **0.12.0** | The `jwt` strategy pins HS256 — check anything minting staff tokens elsewhere. Security events are logged once instead of twice, so counts built on them halve. Password reset now reaches password-less accounts with no social identity |
|
|
1612
|
+
| **0.13.0** | Nothing — and it was never published separately; its changes ship inside 0.14.0 |
|
|
1613
|
+
| **0.14.0** | Biometric authentication is rebuilt and breaking. If you never wired `biometricRepositoryInstance`, nothing changes. If you did, read the security note in the changelog first |
|
|
1614
|
+
|
|
1615
|
+
Two things that are easy to miss because nothing errors:
|
|
1616
|
+
|
|
1617
|
+
- **Security-event counts.** 0.12.0 stopped double-logging `SIGNUP_SUCCESS`,
|
|
1618
|
+
`LOGIN_SUCCESS` and `LOGOUT_SUCCESS`. Dashboards built before it were reading
|
|
1619
|
+
double and will step down on upgrade rather than when you fix the data.
|
|
1620
|
+
- **`verification.baseUrl`.** If you run realms and serve more than one frontend,
|
|
1621
|
+
it accepts a resolver from 0.13.0. Before that, every realm got the same host.
|
|
1622
|
+
|
|
1623
|
+
Take them in order rather than jumping straight to 0.14.0 — each note assumes the
|
|
1624
|
+
one before it.
|
|
1625
|
+
|
|
1626
|
+
## Migrating to v0.12.0
|
|
1627
|
+
|
|
1628
|
+
**Password reset now reaches accounts that have no password and no social login.** The guard on `requestPasswordReset` and `resetPassword` tested `passwordHash == null` and treated it as "this user signed in with Google". That is also how an operator-provisioned account starts, so an account created by an admin CLI was skipped silently — the request returned the same success message as a real send, and the reset would have been refused even if a code had arrived. Both guards now test for an actual social identity (`googleId`, `facebookId` or `appleId`), so a social-only account behaves exactly as before, and a password-less account with no social identity receives a reset code and can redeem it.
|
|
1629
|
+
|
|
1630
|
+
Two consequences worth deciding on rather than discovering. A half-finished signup row with no password and no social identity can now be claimed by whoever controls the address — previously it was unreachable, which is not the same as safe. And if you wrote a placeholder password hash to work around the old behaviour, you can drop it; `hasSocialIdentity` is exported if you want the same predicate.
|
|
1631
|
+
|
|
1632
|
+
The public response is unchanged in both cases. `requestPasswordReset` still returns the same generic message whether it sent, skipped or found nothing, because it is reachable unauthenticated and anything else would be an enumeration oracle. A skip is reported to your `IAuthLogger` instead, as `PASSWORD_RESET_REQUESTED` with `result: 'SKIPPED_SOCIAL_ONLY'`.
|
|
1633
|
+
|
|
1634
|
+
**`verification.baseUrl` accepts a resolver.** Purely additive — a string behaves exactly as before. See [`baseUrl` per realm](#one-api-several-brands-baseurl-per-realm).
|
|
1635
|
+
|
|
1636
|
+
**The `jwt` strategy now pins `HS256`.** That is the algorithm `JwtModule` signs with, so every token this package issues keeps working. A token signed with `jwtSecret` under HS384 or HS512 -- which jsonwebtoken accepted while nothing was pinned -- is now rejected. If another system mints tokens for the `jwt` strategy, check that it signs HS256, or give it its own strategy with [`createExternalJwtStrategy`](#external-jwts).
|
|
1637
|
+
|
|
1638
|
+
**Each security event is logged once.** `BaseAuthResolver` logged `SIGNUP_SUCCESS`, `LOGIN_SUCCESS` and `LOGOUT_SUCCESS` on top of `AuthService`, so an `IAuthLogger` writing to an audit table stored two rows per signup, login and logout. The service's copy is the one that remains: it also covers flows that never reach the resolver. Counts built on those events halve, and `LOGOUT_SUCCESS` metadata is now `{ userId }` -- the resolver's copy also carried `email`. `SIGNUP_ATTEMPT` and `LOGOUT_ALL`, which only the resolver logs, are unchanged.
|
|
1639
|
+
|
|
1640
|
+
**A signup refused by `features.preventEnumerationOnSignup` no longer logs `SIGNUP_SUCCESS`.** It used to log one, with the synthetic response's random user ID, for an account that was never created.
|
|
1641
|
+
|
|
1642
|
+
**API keys.** Nothing changes until you set `apiKey.prefix` or `apiKey.headerName`, or store `scopes` / `expiresAt`. `ApiKeyStrategy`'s constructor takes the `apiKey` options as an optional second argument, which matters only if you construct it yourself.
|
|
1643
|
+
|
|
1236
1644
|
## Migrating to v0.11.0
|
|
1237
1645
|
|
|
1238
1646
|
**`getRequestFromContext` may return `undefined`.** It now also finds a `graphql-ws` subscription's upgrade request, and returns `undefined` where there is no request at all. TypeScript flags each call that assumes a request. In a guard that cannot decide without one, use `requireRequestFromContext`.
|
package/dist/auth.module.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { ITenantRepository } from './interfaces/tenant-repository.interface';
|
|
|
14
14
|
import { ITenantExtractor } from './interfaces/tenant-extractor.interface';
|
|
15
15
|
import { IRealmExtractor } from './interfaces/realm-extractor.interface';
|
|
16
16
|
import { IResourcePermissionRepository } from './interfaces/resource-permission-repository.interface';
|
|
17
|
+
import { IBiometricVerifier } from './interfaces/biometric-verifier.interface';
|
|
17
18
|
import { IJwtPayloadFactory } from './interfaces/jwt-payload-factory.interface';
|
|
18
19
|
import { IApiKeyRepository } from './interfaces/api-key-repository.interface';
|
|
19
20
|
import { PasswordPolicyConfig } from './interfaces/password-policy-config.interface';
|
|
@@ -56,6 +57,10 @@ export interface AuthModuleOptions {
|
|
|
56
57
|
resourcePermissionRepositoryInstance?: IResourcePermissionRepository;
|
|
57
58
|
jwtPayloadFactoryInstance?: IJwtPayloadFactory;
|
|
58
59
|
apiKeyRepositoryInstance?: IApiKeyRepository;
|
|
60
|
+
apiKey?: {
|
|
61
|
+
headerName?: string;
|
|
62
|
+
prefix?: string;
|
|
63
|
+
};
|
|
59
64
|
jwtValidation?: 'full' | 'payload-only';
|
|
60
65
|
isUserActive?: (user: IAuthUser) => boolean;
|
|
61
66
|
bcryptRounds?: number;
|
|
@@ -117,12 +122,21 @@ export interface AuthModuleOptions {
|
|
|
117
122
|
branding?: EmailBrandingConfig;
|
|
118
123
|
templateRenderer?: IEmailTemplateRenderer;
|
|
119
124
|
};
|
|
125
|
+
biometric?: {
|
|
126
|
+
verifiers?: IBiometricVerifier[];
|
|
127
|
+
challengeExpirySeconds?: number;
|
|
128
|
+
challengeRateLimit?: {
|
|
129
|
+
maxAttempts: number;
|
|
130
|
+
windowMs: number;
|
|
131
|
+
};
|
|
132
|
+
};
|
|
120
133
|
verification?: {
|
|
121
134
|
tokenLength?: number;
|
|
122
135
|
tokenExpiresInMinutes?: number;
|
|
123
|
-
baseUrl?: string;
|
|
136
|
+
baseUrl?: string | VerificationBaseUrlResolver;
|
|
124
137
|
};
|
|
125
138
|
}
|
|
139
|
+
export type VerificationBaseUrlResolver = (realm?: string) => string;
|
|
126
140
|
export interface AuthModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
127
141
|
useFactory: (...args: any[]) => Promise<AuthModuleOptions> | AuthModuleOptions;
|
|
128
142
|
inject?: any[];
|
|
@@ -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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"}
|