@furystack/security 7.0.9 → 8.0.1

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.
Files changed (90) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +9 -3
  3. package/esm/crypto-password-hasher.d.ts +10 -12
  4. package/esm/crypto-password-hasher.d.ts.map +1 -1
  5. package/esm/crypto-password-hasher.js +43 -36
  6. package/esm/crypto-password-hasher.js.map +1 -1
  7. package/esm/errors/password-complexity-error.d.ts +5 -4
  8. package/esm/errors/password-complexity-error.d.ts.map +1 -1
  9. package/esm/errors/password-complexity-error.js +5 -4
  10. package/esm/errors/password-complexity-error.js.map +1 -1
  11. package/esm/errors/unauthenticated-error.d.ts +3 -1
  12. package/esm/errors/unauthenticated-error.d.ts.map +1 -1
  13. package/esm/errors/unauthenticated-error.js +3 -1
  14. package/esm/errors/unauthenticated-error.js.map +1 -1
  15. package/esm/helpers.d.ts +20 -7
  16. package/esm/helpers.d.ts.map +1 -1
  17. package/esm/helpers.js +27 -13
  18. package/esm/helpers.js.map +1 -1
  19. package/esm/index.d.ts +3 -1
  20. package/esm/index.d.ts.map +1 -1
  21. package/esm/index.js +3 -1
  22. package/esm/index.js.map +1 -1
  23. package/esm/models/password-credential.d.ts +3 -13
  24. package/esm/models/password-credential.d.ts.map +1 -1
  25. package/esm/models/password-credential.js +3 -1
  26. package/esm/models/password-credential.js.map +1 -1
  27. package/esm/models/password-reset-token.d.ts +5 -9
  28. package/esm/models/password-reset-token.d.ts.map +1 -1
  29. package/esm/models/password-reset-token.js +5 -0
  30. package/esm/models/password-reset-token.js.map +1 -1
  31. package/esm/password-authenticator.d.ts +26 -15
  32. package/esm/password-authenticator.d.ts.map +1 -1
  33. package/esm/password-authenticator.js +73 -109
  34. package/esm/password-authenticator.js.map +1 -1
  35. package/esm/password-authenticator.spec.js +122 -212
  36. package/esm/password-authenticator.spec.js.map +1 -1
  37. package/esm/password-complexity-rules/contains-lowercase.d.ts +2 -2
  38. package/esm/password-complexity-rules/contains-lowercase.js +2 -2
  39. package/esm/password-complexity-rules/contains-uppercase.d.ts +2 -2
  40. package/esm/password-complexity-rules/contains-uppercase.js +2 -2
  41. package/esm/password-complexity-rules/max-length.d.ts +1 -4
  42. package/esm/password-complexity-rules/max-length.d.ts.map +1 -1
  43. package/esm/password-complexity-rules/max-length.js +1 -4
  44. package/esm/password-complexity-rules/max-length.js.map +1 -1
  45. package/esm/password-complexity-rules/min-length.d.ts +1 -4
  46. package/esm/password-complexity-rules/min-length.d.ts.map +1 -1
  47. package/esm/password-complexity-rules/min-length.js +1 -4
  48. package/esm/password-complexity-rules/min-length.js.map +1 -1
  49. package/esm/password-credential-store.d.ts +50 -0
  50. package/esm/password-credential-store.d.ts.map +1 -0
  51. package/esm/password-credential-store.js +73 -0
  52. package/esm/password-credential-store.js.map +1 -0
  53. package/esm/password-credential-store.spec.d.ts +2 -0
  54. package/esm/password-credential-store.spec.d.ts.map +1 -0
  55. package/esm/password-credential-store.spec.js +64 -0
  56. package/esm/password-credential-store.spec.js.map +1 -0
  57. package/esm/password-hasher.d.ts +21 -3
  58. package/esm/password-hasher.d.ts.map +1 -1
  59. package/esm/password-hasher.js +1 -19
  60. package/esm/password-hasher.js.map +1 -1
  61. package/esm/security-policy-manager.d.ts +46 -14
  62. package/esm/security-policy-manager.d.ts.map +1 -1
  63. package/esm/security-policy-manager.js +48 -52
  64. package/esm/security-policy-manager.js.map +1 -1
  65. package/esm/security-policy-manager.spec.js +58 -7
  66. package/esm/security-policy-manager.spec.js.map +1 -1
  67. package/esm/security-policy.d.ts +35 -9
  68. package/esm/security-policy.d.ts.map +1 -1
  69. package/esm/security-policy.js +23 -33
  70. package/esm/security-policy.js.map +1 -1
  71. package/package.json +7 -7
  72. package/src/crypto-password-hasher.ts +46 -39
  73. package/src/errors/password-complexity-error.ts +7 -4
  74. package/src/errors/unauthenticated-error.ts +3 -1
  75. package/src/helpers.ts +27 -15
  76. package/src/index.ts +3 -2
  77. package/src/models/password-credential.ts +5 -15
  78. package/src/models/password-reset-token.ts +5 -9
  79. package/src/password-authenticator.spec.ts +134 -254
  80. package/src/password-authenticator.ts +92 -86
  81. package/src/password-complexity-rules/contains-lowercase.ts +2 -2
  82. package/src/password-complexity-rules/contains-uppercase.ts +2 -2
  83. package/src/password-complexity-rules/max-length.ts +1 -4
  84. package/src/password-complexity-rules/min-length.ts +1 -4
  85. package/src/password-credential-store.spec.ts +77 -0
  86. package/src/password-credential-store.ts +79 -0
  87. package/src/password-hasher.ts +21 -9
  88. package/src/security-policy-manager.spec.ts +63 -7
  89. package/src/security-policy-manager.ts +91 -42
  90. package/src/security-policy.ts +50 -18
@@ -0,0 +1,50 @@
1
+ import { type StoreToken } from '@furystack/core';
2
+ import { type DataSetToken } from '@furystack/repository';
3
+ import { PasswordCredential } from './models/password-credential.js';
4
+ import { PasswordResetToken } from './models/password-reset-token.js';
5
+ /**
6
+ * Error thrown by the default factories of {@link PasswordCredentialStore}
7
+ * and {@link PasswordResetTokenStore} when they are resolved without a
8
+ * caller-provided override.
9
+ *
10
+ * Password credentials and reset tokens **must** survive process restarts,
11
+ * so security refuses to fall back to an in-memory store. Applications
12
+ * must bind a persistent implementation before resolving the
13
+ * {@link PasswordAuthenticator}.
14
+ */
15
+ export declare class SecurityStoreNotConfiguredError extends Error {
16
+ constructor(storeName: string);
17
+ }
18
+ /**
19
+ * Store token for persisted {@link PasswordCredential} records. The default
20
+ * factory deliberately throws — applications are expected to rebind this
21
+ * token on their root injector with a persistent backing store before
22
+ * resolving any service that depends on it.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * injector.bind(PasswordCredentialStore, () => myPersistentCredentialStore)
27
+ * ```
28
+ */
29
+ export declare const PasswordCredentialStore: StoreToken<PasswordCredential, 'userName'>;
30
+ /**
31
+ * Store token for persisted {@link PasswordResetToken} records. Shares the
32
+ * throw-by-default contract with {@link PasswordCredentialStore}.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * injector.bind(PasswordResetTokenStore, () => myPersistentTokenStore)
37
+ * ```
38
+ */
39
+ export declare const PasswordResetTokenStore: StoreToken<PasswordResetToken, 'token'>;
40
+ /**
41
+ * Data set token over {@link PasswordCredentialStore}. The security services
42
+ * route all credential I/O through this dataset so authorization, hooks and
43
+ * change events can be layered on top of the raw store.
44
+ */
45
+ export declare const PasswordCredentialDataSet: DataSetToken<PasswordCredential, 'userName'>;
46
+ /**
47
+ * Data set token over {@link PasswordResetTokenStore}.
48
+ */
49
+ export declare const PasswordResetTokenDataSet: DataSetToken<PasswordResetToken, 'token'>;
50
+ //# sourceMappingURL=password-credential-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-credential-store.d.ts","sourceRoot":"","sources":["../src/password-credential-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAErE;;;;;;;;;GASG;AACH,qBAAa,+BAAgC,SAAQ,KAAK;gBAC5C,SAAS,EAAE,MAAM;CAM9B;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,uBAAuB,EAAE,UAAU,CAAC,kBAAkB,EAAE,UAAU,CAO7E,CAAA;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,UAAU,CAAC,kBAAkB,EAAE,OAAO,CAO1E,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,EAAE,YAAY,CAAC,kBAAkB,EAAE,UAAU,CAGjF,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAE,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAG9E,CAAA"}
@@ -0,0 +1,73 @@
1
+ import { defineStore } from '@furystack/core';
2
+ import { defineDataSet } from '@furystack/repository';
3
+ import { PasswordCredential } from './models/password-credential.js';
4
+ import { PasswordResetToken } from './models/password-reset-token.js';
5
+ /**
6
+ * Error thrown by the default factories of {@link PasswordCredentialStore}
7
+ * and {@link PasswordResetTokenStore} when they are resolved without a
8
+ * caller-provided override.
9
+ *
10
+ * Password credentials and reset tokens **must** survive process restarts,
11
+ * so security refuses to fall back to an in-memory store. Applications
12
+ * must bind a persistent implementation before resolving the
13
+ * {@link PasswordAuthenticator}.
14
+ */
15
+ export class SecurityStoreNotConfiguredError extends Error {
16
+ constructor(storeName) {
17
+ super(`Security store '${storeName}' has not been configured. Bind a persistent PhysicalStore for ${storeName} before resolving the PasswordAuthenticator.`);
18
+ this.name = 'SecurityStoreNotConfiguredError';
19
+ }
20
+ }
21
+ /**
22
+ * Store token for persisted {@link PasswordCredential} records. The default
23
+ * factory deliberately throws — applications are expected to rebind this
24
+ * token on their root injector with a persistent backing store before
25
+ * resolving any service that depends on it.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * injector.bind(PasswordCredentialStore, () => myPersistentCredentialStore)
30
+ * ```
31
+ */
32
+ export const PasswordCredentialStore = defineStore({
33
+ name: 'furystack/security/PasswordCredentialStore',
34
+ model: PasswordCredential,
35
+ primaryKey: 'userName',
36
+ factory: () => {
37
+ throw new SecurityStoreNotConfiguredError('PasswordCredentialStore');
38
+ },
39
+ });
40
+ /**
41
+ * Store token for persisted {@link PasswordResetToken} records. Shares the
42
+ * throw-by-default contract with {@link PasswordCredentialStore}.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * injector.bind(PasswordResetTokenStore, () => myPersistentTokenStore)
47
+ * ```
48
+ */
49
+ export const PasswordResetTokenStore = defineStore({
50
+ name: 'furystack/security/PasswordResetTokenStore',
51
+ model: PasswordResetToken,
52
+ primaryKey: 'token',
53
+ factory: () => {
54
+ throw new SecurityStoreNotConfiguredError('PasswordResetTokenStore');
55
+ },
56
+ });
57
+ /**
58
+ * Data set token over {@link PasswordCredentialStore}. The security services
59
+ * route all credential I/O through this dataset so authorization, hooks and
60
+ * change events can be layered on top of the raw store.
61
+ */
62
+ export const PasswordCredentialDataSet = defineDataSet({
63
+ name: 'furystack/security/PasswordCredentialDataSet',
64
+ store: PasswordCredentialStore,
65
+ });
66
+ /**
67
+ * Data set token over {@link PasswordResetTokenStore}.
68
+ */
69
+ export const PasswordResetTokenDataSet = defineDataSet({
70
+ name: 'furystack/security/PasswordResetTokenDataSet',
71
+ store: PasswordResetTokenStore,
72
+ });
73
+ //# sourceMappingURL=password-credential-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-credential-store.js","sourceRoot":"","sources":["../src/password-credential-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAmB,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAqB,MAAM,uBAAuB,CAAA;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAErE;;;;;;;;;GASG;AACH,MAAM,OAAO,+BAAgC,SAAQ,KAAK;IACxD,YAAY,SAAiB;QAC3B,KAAK,CACH,mBAAmB,SAAS,kEAAkE,SAAS,8CAA8C,CACtJ,CAAA;QACD,IAAI,CAAC,IAAI,GAAG,iCAAiC,CAAA;IAC/C,CAAC;CACF;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA+C,WAAW,CAAC;IAC7F,IAAI,EAAE,4CAA4C;IAClD,KAAK,EAAE,kBAAkB;IACzB,UAAU,EAAE,UAAU;IACtB,OAAO,EAAE,GAAG,EAAE;QACZ,MAAM,IAAI,+BAA+B,CAAC,yBAAyB,CAAC,CAAA;IACtE,CAAC;CACF,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAA4C,WAAW,CAAC;IAC1F,IAAI,EAAE,4CAA4C;IAClD,KAAK,EAAE,kBAAkB;IACzB,UAAU,EAAE,OAAO;IACnB,OAAO,EAAE,GAAG,EAAE;QACZ,MAAM,IAAI,+BAA+B,CAAC,yBAAyB,CAAC,CAAA;IACtE,CAAC;CACF,CAAC,CAAA;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAiD,aAAa,CAAC;IACnG,IAAI,EAAE,8CAA8C;IACpD,KAAK,EAAE,uBAAuB;CAC/B,CAAC,CAAA;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAA8C,aAAa,CAAC;IAChG,IAAI,EAAE,8CAA8C;IACpD,KAAK,EAAE,uBAAuB;CAC/B,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=password-credential-store.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-credential-store.spec.d.ts","sourceRoot":"","sources":["../src/password-credential-store.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { InMemoryStore } from '@furystack/core';
2
+ import { createInjector } from '@furystack/inject';
3
+ import { usingAsync } from '@furystack/utils';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { PasswordCredential } from './models/password-credential.js';
6
+ import { PasswordResetToken } from './models/password-reset-token.js';
7
+ import { PasswordCredentialDataSet, PasswordCredentialStore, PasswordResetTokenDataSet, PasswordResetTokenStore, SecurityStoreNotConfiguredError, } from './password-credential-store.js';
8
+ describe('PasswordCredentialStore', () => {
9
+ it('exposes the PasswordCredential model and userName primary key on the token', () => {
10
+ expect(PasswordCredentialStore.model).toBe(PasswordCredential);
11
+ expect(PasswordCredentialStore.primaryKey).toBe('userName');
12
+ expect(PasswordCredentialStore.lifetime).toBe('singleton');
13
+ });
14
+ it('throws SecurityStoreNotConfiguredError when resolved without an override', async () => {
15
+ await usingAsync(createInjector(), async (injector) => {
16
+ expect(() => injector.get(PasswordCredentialStore)).toThrow(SecurityStoreNotConfiguredError);
17
+ expect(() => injector.get(PasswordCredentialStore)).toThrow(/PasswordCredentialStore.*not been configured/);
18
+ });
19
+ });
20
+ it('returns the bound implementation after rebinding the token', async () => {
21
+ await usingAsync(createInjector(), async (injector) => {
22
+ const impl = new InMemoryStore({ model: PasswordCredential, primaryKey: 'userName' });
23
+ injector.bind(PasswordCredentialStore, () => impl);
24
+ expect(injector.get(PasswordCredentialStore)).toBe(impl);
25
+ });
26
+ });
27
+ });
28
+ describe('PasswordResetTokenStore', () => {
29
+ it('exposes the PasswordResetToken model and token primary key on the token', () => {
30
+ expect(PasswordResetTokenStore.model).toBe(PasswordResetToken);
31
+ expect(PasswordResetTokenStore.primaryKey).toBe('token');
32
+ expect(PasswordResetTokenStore.lifetime).toBe('singleton');
33
+ });
34
+ it('throws SecurityStoreNotConfiguredError when resolved without an override', async () => {
35
+ await usingAsync(createInjector(), async (injector) => {
36
+ expect(() => injector.get(PasswordResetTokenStore)).toThrow(SecurityStoreNotConfiguredError);
37
+ expect(() => injector.get(PasswordResetTokenStore)).toThrow(/PasswordResetTokenStore.*not been configured/);
38
+ });
39
+ });
40
+ it('returns the bound implementation after rebinding the token', async () => {
41
+ await usingAsync(createInjector(), async (injector) => {
42
+ const impl = new InMemoryStore({ model: PasswordResetToken, primaryKey: 'token' });
43
+ injector.bind(PasswordResetTokenStore, () => impl);
44
+ expect(injector.get(PasswordResetTokenStore)).toBe(impl);
45
+ });
46
+ });
47
+ });
48
+ describe('PasswordCredentialDataSet / PasswordResetTokenDataSet', () => {
49
+ it('mirror the backing store metadata onto the data-set token', () => {
50
+ expect(PasswordCredentialDataSet.model).toBe(PasswordCredential);
51
+ expect(PasswordCredentialDataSet.primaryKey).toBe('userName');
52
+ expect(PasswordResetTokenDataSet.model).toBe(PasswordResetToken);
53
+ expect(PasswordResetTokenDataSet.primaryKey).toBe('token');
54
+ });
55
+ });
56
+ describe('SecurityStoreNotConfiguredError', () => {
57
+ it('embeds the store name in the message and sets the error name', () => {
58
+ const error = new SecurityStoreNotConfiguredError('SomeStore');
59
+ expect(error.name).toBe('SecurityStoreNotConfiguredError');
60
+ expect(error.message).toContain('SomeStore');
61
+ expect(error).toBeInstanceOf(Error);
62
+ });
63
+ });
64
+ //# sourceMappingURL=password-credential-store.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-credential-store.spec.js","sourceRoot":"","sources":["../src/password-credential-store.spec.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,24 @@
1
1
  import type { PasswordCheckResult, PasswordCredential } from './models/index.js';
2
- export declare class PasswordHasher {
3
- createCredential(_userName: string, _password: string): Promise<PasswordCredential>;
4
- verifyCredential(_password: string, _credential: PasswordCredential): Promise<PasswordCheckResult>;
2
+ /**
3
+ * Abstraction for hashing and verifying password credentials.
4
+ *
5
+ * Implementations are DI tokens that resolve to an object conforming to this
6
+ * interface. The default implementation shipped by `@furystack/security` is
7
+ * {@link CryptoPasswordHasher}; applications can swap in an alternative by
8
+ * binding the {@link SecurityPolicy.hasher} token (for example, to use a
9
+ * stronger KDF or a managed hashing service).
10
+ */
11
+ export interface PasswordHasher {
12
+ /**
13
+ * Produces a fresh credential entry for the given user name and plain
14
+ * password. The returned record is suitable for persistence in the
15
+ * credential store.
16
+ */
17
+ createCredential(userName: string, password: string): Promise<PasswordCredential>;
18
+ /**
19
+ * Verifies a plain password against a stored credential. Implementations
20
+ * should be constant-time relative to the password to avoid timing leaks.
21
+ */
22
+ verifyCredential(password: string, credential: PasswordCredential): Promise<PasswordCheckResult>;
5
23
  }
6
24
  //# sourceMappingURL=password-hasher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"password-hasher.d.ts","sourceRoot":"","sources":["../src/password-hasher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEhF,qBACa,cAAc;IAClB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAInF,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAG1G"}
1
+ {"version":3,"file":"password-hasher.d.ts","sourceRoot":"","sources":["../src/password-hasher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEhF;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAEjF;;;OAGG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAA;CACjG"}
@@ -1,20 +1,2 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- import { Injectable } from '@furystack/inject';
8
- let PasswordHasher = class PasswordHasher {
9
- createCredential(_userName, _password) {
10
- throw Error('Set up hasher - TODO');
11
- }
12
- verifyCredential(_password, _credential) {
13
- throw Error('Set up hasher - TODO');
14
- }
15
- };
16
- PasswordHasher = __decorate([
17
- Injectable({ lifetime: 'singleton' })
18
- ], PasswordHasher);
19
- export { PasswordHasher };
1
+ export {};
20
2
  //# sourceMappingURL=password-hasher.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"password-hasher.js","sourceRoot":"","sources":["../src/password-hasher.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAIvC,IAAM,cAAc,GAApB,MAAM,cAAc;IAClB,gBAAgB,CAAC,SAAiB,EAAE,SAAiB;QAC1D,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAA;IACrC,CAAC;IAEM,gBAAgB,CAAC,SAAiB,EAAE,WAA+B;QACxE,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAA;IACrC,CAAC;CACF,CAAA;AARY,cAAc;IAD1B,UAAU,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;GACzB,cAAc,CAQ1B"}
1
+ {"version":3,"file":"password-hasher.js","sourceRoot":"","sources":["../src/password-hasher.ts"],"names":[],"mappings":""}
@@ -1,22 +1,54 @@
1
+ import { type Token } from '@furystack/inject';
1
2
  import type { PasswordCredential, PasswordResetToken } from './models/index.js';
2
3
  import { SecurityPolicy } from './security-policy.js';
3
- export declare class SecurityPolicyManager {
4
+ /**
5
+ * Result of a successful password complexity check — no rule reported a
6
+ * failure.
7
+ */
8
+ export type PasswordComplexityMatch = {
9
+ match: true;
10
+ };
11
+ /**
12
+ * Result of a failed password complexity check along with the individual
13
+ * rule failures for user-facing reporting.
14
+ */
15
+ export type PasswordComplexityMismatch = {
16
+ match: false;
17
+ errors: Array<{
18
+ message: string;
19
+ rule: string;
20
+ }>;
21
+ };
22
+ /**
23
+ * Runtime checker for the active {@link SecurityPolicy}. Applies complexity
24
+ * rules to candidate passwords and determines whether stored credentials
25
+ * and reset tokens have expired.
26
+ */
27
+ export interface SecurityPolicyManager {
28
+ /** The active {@link SecurityPolicy} snapshot captured at resolve time. */
29
+ readonly policy: SecurityPolicy;
4
30
  /**
5
- * @param password The Plain password string
6
- * @returns whether the password matches the complexity rules
31
+ * Evaluates every complexity rule against `password` and aggregates the
32
+ * result. `match: true` means the password is acceptable.
33
+ */
34
+ matchPasswordComplexityRules(password: string): Promise<PasswordComplexityMatch | PasswordComplexityMismatch>;
35
+ /**
36
+ * Returns `true` when `credential.creationDate` plus
37
+ * `policy.passwordExpirationDays` is in the past. Always `false` when the
38
+ * policy sets `passwordExpirationDays` to `0`.
7
39
  */
8
- matchPasswordComplexityRules(password: string): Promise<{
9
- match: false;
10
- errors: Array<{
11
- message: string;
12
- rule: string;
13
- }>;
14
- } | {
15
- match: true;
16
- errors?: undefined;
17
- }>;
18
40
  hasPasswordExpired(credential: PasswordCredential): boolean;
41
+ /**
42
+ * Returns `true` when `token.createdAt` plus
43
+ * `policy.resetTokenExpirationSeconds` is in the past. Always `false`
44
+ * when the policy sets `resetTokenExpirationSeconds` to `0`.
45
+ */
19
46
  hasTokenExpired(token: PasswordResetToken): boolean;
20
- readonly policy: SecurityPolicy;
21
47
  }
48
+ /**
49
+ * DI token for the singleton {@link SecurityPolicyManager}. Resolves the
50
+ * current {@link SecurityPolicy} once and closes over it for the lifetime
51
+ * of the owning injector.
52
+ */
53
+ export declare const SecurityPolicyManager: Token<SecurityPolicyManager, 'singleton'>;
22
54
  //# sourceMappingURL=security-policy-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"security-policy-manager.d.ts","sourceRoot":"","sources":["../src/security-policy-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD,qBACa,qBAAqB;IAChC;;;OAGG;IACU,4BAA4B,CAAC,QAAQ,EAAE,MAAM;;gBAMsC,KAAK,CAAC;YAC9F,OAAO,EAAE,MAAM,CAAA;YACf,IAAI,EAAE,MAAM,CAAA;SACb,CAAC;;;;;IASD,kBAAkB,CAAC,UAAU,EAAE,kBAAkB;IAUjD,eAAe,CAAC,KAAK,EAAE,kBAAkB;IAUhD,SACwB,MAAM,EAAE,cAAc,CAAA;CAC/C"}
1
+ {"version":3,"file":"security-policy-manager.d.ts","sourceRoot":"","sources":["../src/security-policy-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAC7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErD;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,CAAA;AAErD;;;GAGG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,KAAK,CAAA;IACZ,MAAM,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACjD,CAAA;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B;;;OAGG;IACH,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,0BAA0B,CAAC,CAAA;IAC7G;;;;OAIG;IACH,kBAAkB,CAAC,UAAU,EAAE,kBAAkB,GAAG,OAAO,CAAA;IAC3D;;;;OAIG;IACH,eAAe,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAA;CACpD;AAcD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,qBAAqB,EAAE,WAAW,CAoC1E,CAAA"}
@@ -1,57 +1,53 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1
+ import { defineService } from '@furystack/inject';
2
+ import { SecurityPolicy } from './security-policy.js';
3
+ const addDays = (base, days) => {
4
+ const result = new Date(base);
5
+ result.setDate(result.getDate() + days);
6
+ return result;
6
7
  };
7
- var __metadata = (this && this.__metadata) || function (k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
8
+ const addSeconds = (base, seconds) => {
9
+ const result = new Date(base);
10
+ result.setSeconds(result.getSeconds() + seconds);
11
+ return result;
9
12
  };
10
- import { Injectable, Injected } from '@furystack/inject';
11
- import { SecurityPolicy } from './security-policy.js';
12
- let SecurityPolicyManager = class SecurityPolicyManager {
13
- /**
14
- * @param password The Plain password string
15
- * @returns whether the password matches the complexity rules
16
- */
17
- async matchPasswordComplexityRules(password) {
18
- const result = await Promise.all(this.policy.passwordComplexityRules.map((rule) => rule.check(password)));
19
- const failed = result.filter((r) => r.success === false);
20
- if (failed.length) {
21
- return {
22
- match: false,
23
- errors: failed.map((f) => f.success === false && { message: f.message, rule: f.rule }),
24
- };
25
- }
13
+ /**
14
+ * DI token for the singleton {@link SecurityPolicyManager}. Resolves the
15
+ * current {@link SecurityPolicy} once and closes over it for the lifetime
16
+ * of the owning injector.
17
+ */
18
+ export const SecurityPolicyManager = defineService({
19
+ name: 'furystack/security/SecurityPolicyManager',
20
+ lifetime: 'singleton',
21
+ factory: ({ inject }) => {
22
+ const policy = inject(SecurityPolicy);
26
23
  return {
27
- match: true,
24
+ policy,
25
+ matchPasswordComplexityRules: async (password) => {
26
+ const outcomes = await Promise.all(policy.passwordComplexityRules.map((rule) => rule.check(password)));
27
+ const failed = outcomes.filter((outcome) => outcome.success === false);
28
+ if (failed.length === 0) {
29
+ return { match: true };
30
+ }
31
+ return {
32
+ match: false,
33
+ errors: failed.map((outcome) => outcome.success === false ? { message: outcome.message, rule: outcome.rule } : { message: '', rule: '' }),
34
+ };
35
+ },
36
+ hasPasswordExpired: (credential) => {
37
+ if (!policy.passwordExpirationDays) {
38
+ return false;
39
+ }
40
+ const expiration = addDays(new Date(credential.creationDate), policy.passwordExpirationDays);
41
+ return new Date() > expiration;
42
+ },
43
+ hasTokenExpired: (token) => {
44
+ if (!policy.resetTokenExpirationSeconds) {
45
+ return false;
46
+ }
47
+ const expiration = addSeconds(new Date(token.createdAt), policy.resetTokenExpirationSeconds);
48
+ return new Date() > expiration;
49
+ },
28
50
  };
29
- }
30
- hasPasswordExpired(credential) {
31
- if (!this.policy.passwordExpirationDays) {
32
- return false;
33
- }
34
- const credentialExpiration = new Date(credential.creationDate);
35
- credentialExpiration.setDate(credentialExpiration.getDate() + this.policy.passwordExpirationDays);
36
- const now = new Date();
37
- return now > credentialExpiration;
38
- }
39
- hasTokenExpired(token) {
40
- if (!this.policy.resetTokenExpirationSeconds) {
41
- return false;
42
- }
43
- const tokenExpiration = new Date(token.createdAt);
44
- tokenExpiration.setSeconds(tokenExpiration.getSeconds() + this.policy.resetTokenExpirationSeconds);
45
- const now = new Date();
46
- return now > tokenExpiration;
47
- }
48
- };
49
- __decorate([
50
- Injected(SecurityPolicy),
51
- __metadata("design:type", SecurityPolicy)
52
- ], SecurityPolicyManager.prototype, "policy", void 0);
53
- SecurityPolicyManager = __decorate([
54
- Injectable({ lifetime: 'singleton' })
55
- ], SecurityPolicyManager);
56
- export { SecurityPolicyManager };
51
+ },
52
+ });
57
53
  //# sourceMappingURL=security-policy-manager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"security-policy-manager.js","sourceRoot":"","sources":["../src/security-policy-manager.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAExD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAG9C,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC;;;OAGG;IACI,KAAK,CAAC,4BAA4B,CAAC,QAAgB;QACxD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;QACzG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC,CAAA;QACxD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO;gBACL,KAAK,EAAE,KAAc;gBACrB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAGnF;aACH,CAAA;QACH,CAAC;QAED,OAAO;YACL,KAAK,EAAE,IAAa;SACrB,CAAA;IACH,CAAC;IAEM,kBAAkB,CAAC,UAA8B;QACtD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE,CAAC;YACxC,OAAO,KAAK,CAAA;QACd,CAAC;QACD,MAAM,oBAAoB,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,CAAA;QAC9D,oBAAoB,CAAC,OAAO,CAAC,oBAAoB,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAA;QACjG,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;QACtB,OAAO,GAAG,GAAG,oBAAoB,CAAA;IACnC,CAAC;IAEM,eAAe,CAAC,KAAyB;QAC9C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,2BAA2B,EAAE,CAAC;YAC7C,OAAO,KAAK,CAAA;QACd,CAAC;QACD,MAAM,eAAe,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QACjD,eAAe,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAA;QAClG,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;QACtB,OAAO,GAAG,GAAG,eAAe,CAAA;IAC9B,CAAC;CAIF,CAAA;AADyB;IADvB,QAAQ,CAAC,cAAc,CAAC;8BACO,cAAc;qDAAA;AA5CnC,qBAAqB;IADjC,UAAU,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;GACzB,qBAAqB,CA6CjC"}
1
+ {"version":3,"file":"security-policy-manager.js","sourceRoot":"","sources":["../src/security-policy-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAc,MAAM,mBAAmB,CAAA;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AA4CrD,MAAM,OAAO,GAAG,CAAC,IAAU,EAAE,IAAY,EAAQ,EAAE;IACjD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAA;IACvC,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,UAAU,GAAG,CAAC,IAAU,EAAE,OAAe,EAAQ,EAAE;IACvD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7B,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,OAAO,CAAC,CAAA;IAChD,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAA8C,aAAa,CAAC;IAC5F,IAAI,EAAE,0CAA0C;IAChD,QAAQ,EAAE,WAAW;IACrB,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAyB,EAAE;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAA;QACrC,OAAO;YACL,MAAM;YACN,4BAA4B,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;gBAC/C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,uBAAuB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;gBACtG,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC,CAAA;gBACtE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACxB,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;gBACxB,CAAC;gBACD,OAAO;oBACL,KAAK,EAAE,KAAK;oBACZ,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAC7B,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CACzG;iBACF,CAAA;YACH,CAAC;YACD,kBAAkB,EAAE,CAAC,UAAU,EAAE,EAAE;gBACjC,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE,CAAC;oBACnC,OAAO,KAAK,CAAA;gBACd,CAAC;gBACD,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,sBAAsB,CAAC,CAAA;gBAC5F,OAAO,IAAI,IAAI,EAAE,GAAG,UAAU,CAAA;YAChC,CAAC;YACD,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE;gBACzB,IAAI,CAAC,MAAM,CAAC,2BAA2B,EAAE,CAAC;oBACxC,OAAO,KAAK,CAAA;gBACd,CAAC;gBACD,MAAM,UAAU,GAAG,UAAU,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,2BAA2B,CAAC,CAAA;gBAC5F,OAAO,IAAI,IAAI,EAAE,GAAG,UAAU,CAAA;YAChC,CAAC;SACF,CAAA;IACH,CAAC;CACF,CAAC,CAAA"}
@@ -1,16 +1,67 @@
1
- import { Injector } from '@furystack/inject';
1
+ import { createInjector } from '@furystack/inject';
2
2
  import { usingAsync } from '@furystack/utils';
3
3
  import { describe, expect, it } from 'vitest';
4
4
  import { usePasswordPolicy } from './helpers.js';
5
5
  import { SecurityPolicyManager } from './security-policy-manager.js';
6
6
  describe('SecurityPolicyManager', () => {
7
- it('Should return false when no token expiration has been set', async () => {
8
- await usingAsync(new Injector(), async (i) => {
7
+ it('returns a cached manager backed by the current policy', async () => {
8
+ await usingAsync(createInjector(), async (i) => {
9
+ usePasswordPolicy(i, { passwordExpirationDays: 30 });
10
+ const manager = i.get(SecurityPolicyManager);
11
+ expect(manager.policy.passwordExpirationDays).toBe(30);
12
+ });
13
+ });
14
+ it('never considers a token expired when resetTokenExpirationSeconds is 0', async () => {
15
+ await usingAsync(createInjector(), async (i) => {
9
16
  usePasswordPolicy(i, { resetTokenExpirationSeconds: 0 });
10
- const result = i
11
- .getInstance(SecurityPolicyManager)
12
- .hasTokenExpired({ createdAt: new Date().toISOString(), token: 'token', userName: 'user' });
13
- expect(result).toBeFalsy();
17
+ const expired = i
18
+ .get(SecurityPolicyManager)
19
+ .hasTokenExpired({ createdAt: new Date(0).toISOString(), token: 'token', userName: 'user' });
20
+ expect(expired).toBe(false);
21
+ });
22
+ });
23
+ it('flags a token as expired once its lifetime has elapsed', async () => {
24
+ await usingAsync(createInjector(), async (i) => {
25
+ usePasswordPolicy(i, { resetTokenExpirationSeconds: 1 });
26
+ const pastCreatedAt = new Date();
27
+ pastCreatedAt.setSeconds(pastCreatedAt.getSeconds() - 10);
28
+ const expired = i
29
+ .get(SecurityPolicyManager)
30
+ .hasTokenExpired({ createdAt: pastCreatedAt.toISOString(), token: 'token', userName: 'user' });
31
+ expect(expired).toBe(true);
32
+ });
33
+ });
34
+ it('never considers a credential expired when passwordExpirationDays is 0', async () => {
35
+ await usingAsync(createInjector(), async (i) => {
36
+ usePasswordPolicy(i, { passwordExpirationDays: 0 });
37
+ const expired = i.get(SecurityPolicyManager).hasPasswordExpired({
38
+ userName: 'user',
39
+ passwordHash: 'hash',
40
+ salt: 'salt',
41
+ creationDate: new Date(0).toISOString(),
42
+ });
43
+ expect(expired).toBe(false);
44
+ });
45
+ });
46
+ it('flags a credential as expired once its lifetime has elapsed', async () => {
47
+ await usingAsync(createInjector(), async (i) => {
48
+ usePasswordPolicy(i, { passwordExpirationDays: 1 });
49
+ const pastCreationDate = new Date();
50
+ pastCreationDate.setDate(pastCreationDate.getDate() - 5);
51
+ const expired = i.get(SecurityPolicyManager).hasPasswordExpired({
52
+ userName: 'user',
53
+ passwordHash: 'hash',
54
+ salt: 'salt',
55
+ creationDate: pastCreationDate.toISOString(),
56
+ });
57
+ expect(expired).toBe(true);
58
+ });
59
+ });
60
+ it('reports match=true when no complexity rules are configured', async () => {
61
+ await usingAsync(createInjector(), async (i) => {
62
+ usePasswordPolicy(i);
63
+ const result = await i.get(SecurityPolicyManager).matchPasswordComplexityRules('anything');
64
+ expect(result.match).toBe(true);
14
65
  });
15
66
  });
16
67
  });
@@ -1 +1 @@
1
- {"version":3,"file":"security-policy-manager.spec.js","sourceRoot":"","sources":["../src/security-policy-manager.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAA;AAEpE,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,UAAU,CAAC,IAAI,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC3C,iBAAiB,CAAC,CAAC,EAAE,EAAE,2BAA2B,EAAE,CAAC,EAAE,CAAC,CAAA;YACxD,MAAM,MAAM,GAAG,CAAC;iBACb,WAAW,CAAC,qBAAqB,CAAC;iBAClC,eAAe,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;YAC7F,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,CAAA;QAC5B,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"security-policy-manager.spec.js","sourceRoot":"","sources":["../src/security-policy-manager.spec.ts"],"names":[],"mappings":"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"}
@@ -1,26 +1,52 @@
1
- import type { Constructable } from '@furystack/inject';
1
+ import { type Token } from '@furystack/inject';
2
2
  import type { PasswordComplexityRule } from './models/password-complexity-rule.js';
3
3
  import type { PasswordHasher } from './password-hasher.js';
4
- export declare class SecurityPolicy {
4
+ /**
5
+ * Application-wide policy that governs password hashing, complexity rules,
6
+ * expiration and second-factor requirements.
7
+ *
8
+ * The default policy shipped by `@furystack/security` uses
9
+ * {@link CryptoPasswordHasher}, enforces no complexity rules, disables
10
+ * password and reset-token expiration by default and does not require
11
+ * second factor authentication. Override via {@link usePasswordPolicy}.
12
+ */
13
+ export type SecurityPolicy = {
5
14
  /**
6
- * A Hasher instance for password hash generation
15
+ * Token resolving to the {@link PasswordHasher} used for generating and
16
+ * verifying password credentials.
7
17
  */
8
- hasher: Constructable<PasswordHasher>;
18
+ hasher: Token<PasswordHasher, 'singleton'>;
9
19
  /**
10
- * Array of password complexity rules to apply
20
+ * Ordered list of password complexity rules applied to every new or
21
+ * changed password.
11
22
  */
12
23
  passwordComplexityRules: PasswordComplexityRule[];
13
24
  /**
14
- * The password expiration in days
25
+ * Number of days after which a stored password credential is considered
26
+ * expired. `0` disables expiration entirely.
15
27
  */
16
28
  passwordExpirationDays: number;
17
29
  /**
18
- * Defines if the login needs a second factor authentication
30
+ * Second-factor authentication policy. `'always'` forces a second factor
31
+ * on every login; `'never'` disables it. Trusted-device-aware policies
32
+ * are not yet implemented.
19
33
  */
20
34
  requireSecondFactor: 'never' | 'always';
21
35
  /**
22
- * The Password Reset Token expiration in seconds
36
+ * Lifetime of a password reset token in seconds.
23
37
  */
24
38
  resetTokenExpirationSeconds: number;
25
- }
39
+ };
40
+ /**
41
+ * Returns a fresh copy of the default {@link SecurityPolicy}. Useful as a
42
+ * starting point for `Object.assign`-style overrides or spec fixtures.
43
+ */
44
+ export declare const defaultSecurityPolicy: () => SecurityPolicy;
45
+ /**
46
+ * DI token that resolves the current {@link SecurityPolicy}. Rebind this
47
+ * token via {@link usePasswordPolicy} (preferred) or directly through
48
+ * {@link Injector.bind} to customise the policy per-application or
49
+ * per-scope.
50
+ */
51
+ export declare const SecurityPolicy: Token<SecurityPolicy, 'singleton'>;
26
52
  //# sourceMappingURL=security-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"security-policy.d.ts","sourceRoot":"","sources":["../src/security-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGtD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAA;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D,qBACa,cAAc;IACzB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC,cAAc,CAAC,CAAuB;IAE5D;;OAEG;IACH,uBAAuB,EAAE,sBAAsB,EAAE,CAAK;IAEtD;;OAEG;IACH,sBAAsB,SAAI;IAE1B;;OAEG;IACH,mBAAmB,EAAE,OAAO,GAAG,QAAQ,CAAU;IAEjD;;OAEG;IACH,2BAA2B,SAAK;CACjC"}
1
+ {"version":3,"file":"security-policy.d.ts","sourceRoot":"","sources":["../src/security-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE7D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAA;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAE1D;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,MAAM,EAAE,KAAK,CAAC,cAAc,EAAE,WAAW,CAAC,CAAA;IAC1C;;;OAGG;IACH,uBAAuB,EAAE,sBAAsB,EAAE,CAAA;IACjD;;;OAGG;IACH,sBAAsB,EAAE,MAAM,CAAA;IAC9B;;;;OAIG;IACH,mBAAmB,EAAE,OAAO,GAAG,QAAQ,CAAA;IACvC;;OAEG;IACH,2BAA2B,EAAE,MAAM,CAAA;CACpC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAO,cAMvC,CAAA;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,WAAW,CAI5D,CAAA"}