@furystack/security 7.0.9 → 8.0.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 +10 -0
- package/README.md +9 -3
- package/esm/crypto-password-hasher.d.ts +10 -12
- package/esm/crypto-password-hasher.d.ts.map +1 -1
- package/esm/crypto-password-hasher.js +43 -36
- package/esm/crypto-password-hasher.js.map +1 -1
- package/esm/helpers.d.ts +20 -7
- package/esm/helpers.d.ts.map +1 -1
- package/esm/helpers.js +27 -13
- package/esm/helpers.js.map +1 -1
- package/esm/index.d.ts +3 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -1
- package/esm/index.js.map +1 -1
- package/esm/password-authenticator.d.ts +26 -15
- package/esm/password-authenticator.d.ts.map +1 -1
- package/esm/password-authenticator.js +73 -109
- package/esm/password-authenticator.js.map +1 -1
- package/esm/password-authenticator.spec.js +122 -212
- package/esm/password-authenticator.spec.js.map +1 -1
- package/esm/password-credential-store.d.ts +50 -0
- package/esm/password-credential-store.d.ts.map +1 -0
- package/esm/password-credential-store.js +73 -0
- package/esm/password-credential-store.js.map +1 -0
- package/esm/password-credential-store.spec.d.ts +2 -0
- package/esm/password-credential-store.spec.d.ts.map +1 -0
- package/esm/password-credential-store.spec.js +64 -0
- package/esm/password-credential-store.spec.js.map +1 -0
- package/esm/password-hasher.d.ts +21 -3
- package/esm/password-hasher.d.ts.map +1 -1
- package/esm/password-hasher.js +1 -19
- package/esm/password-hasher.js.map +1 -1
- package/esm/security-policy-manager.d.ts +46 -14
- package/esm/security-policy-manager.d.ts.map +1 -1
- package/esm/security-policy-manager.js +48 -52
- package/esm/security-policy-manager.js.map +1 -1
- package/esm/security-policy-manager.spec.js +58 -7
- package/esm/security-policy-manager.spec.js.map +1 -1
- package/esm/security-policy.d.ts +35 -9
- package/esm/security-policy.d.ts.map +1 -1
- package/esm/security-policy.js +23 -33
- package/esm/security-policy.js.map +1 -1
- package/package.json +5 -5
- package/src/crypto-password-hasher.ts +46 -39
- package/src/helpers.ts +27 -15
- package/src/index.ts +3 -2
- package/src/password-authenticator.spec.ts +134 -254
- package/src/password-authenticator.ts +92 -86
- package/src/password-credential-store.spec.ts +77 -0
- package/src/password-credential-store.ts +79 -0
- package/src/password-hasher.ts +21 -9
- package/src/security-policy-manager.spec.ts +63 -7
- package/src/security-policy-manager.ts +91 -42
- package/src/security-policy.ts +50 -18
|
@@ -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 @@
|
|
|
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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"}
|
package/esm/password-hasher.d.ts
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import type { PasswordCheckResult, PasswordCredential } from './models/index.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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":"
|
|
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"}
|
package/esm/password-hasher.js
CHANGED
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
|
|
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":"
|
|
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
|
-
|
|
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
|
-
*
|
|
6
|
-
*
|
|
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":"
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
|
|
8
|
+
const addSeconds = (base, seconds) => {
|
|
9
|
+
const result = new Date(base);
|
|
10
|
+
result.setSeconds(result.getSeconds() + seconds);
|
|
11
|
+
return result;
|
|
9
12
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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 {
|
|
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('
|
|
8
|
-
await usingAsync(
|
|
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
|
|
11
|
-
.
|
|
12
|
-
.hasTokenExpired({ createdAt: new Date().toISOString(), token: 'token', userName: 'user' });
|
|
13
|
-
expect(
|
|
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,
|
|
1
|
+
{"version":3,"file":"security-policy-manager.spec.js","sourceRoot":"","sources":["../src/security-policy-manager.spec.ts"],"names":[],"mappings":"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"}
|
package/esm/security-policy.d.ts
CHANGED
|
@@ -1,26 +1,52 @@
|
|
|
1
|
-
import type
|
|
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
|
-
|
|
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
|
-
*
|
|
15
|
+
* Token resolving to the {@link PasswordHasher} used for generating and
|
|
16
|
+
* verifying password credentials.
|
|
7
17
|
*/
|
|
8
|
-
hasher:
|
|
18
|
+
hasher: Token<PasswordHasher, 'singleton'>;
|
|
9
19
|
/**
|
|
10
|
-
*
|
|
20
|
+
* Ordered list of password complexity rules applied to every new or
|
|
21
|
+
* changed password.
|
|
11
22
|
*/
|
|
12
23
|
passwordComplexityRules: PasswordComplexityRule[];
|
|
13
24
|
/**
|
|
14
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
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"}
|
package/esm/security-policy.js
CHANGED
|
@@ -1,35 +1,25 @@
|
|
|
1
|
-
|
|
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';
|
|
1
|
+
import { defineService } from '@furystack/inject';
|
|
8
2
|
import { CryptoPasswordHasher } from './crypto-password-hasher.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
31
|
-
SecurityPolicy = __decorate([
|
|
32
|
-
Injectable({ lifetime: 'singleton' })
|
|
33
|
-
], SecurityPolicy);
|
|
34
|
-
export { SecurityPolicy };
|
|
3
|
+
/**
|
|
4
|
+
* Returns a fresh copy of the default {@link SecurityPolicy}. Useful as a
|
|
5
|
+
* starting point for `Object.assign`-style overrides or spec fixtures.
|
|
6
|
+
*/
|
|
7
|
+
export const defaultSecurityPolicy = () => ({
|
|
8
|
+
hasher: CryptoPasswordHasher,
|
|
9
|
+
passwordComplexityRules: [],
|
|
10
|
+
passwordExpirationDays: 0,
|
|
11
|
+
requireSecondFactor: 'never',
|
|
12
|
+
resetTokenExpirationSeconds: 15,
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* DI token that resolves the current {@link SecurityPolicy}. Rebind this
|
|
16
|
+
* token via {@link usePasswordPolicy} (preferred) or directly through
|
|
17
|
+
* {@link Injector.bind} to customise the policy per-application or
|
|
18
|
+
* per-scope.
|
|
19
|
+
*/
|
|
20
|
+
export const SecurityPolicy = defineService({
|
|
21
|
+
name: 'furystack/security/SecurityPolicy',
|
|
22
|
+
lifetime: 'singleton',
|
|
23
|
+
factory: () => defaultSecurityPolicy(),
|
|
24
|
+
});
|
|
35
25
|
//# sourceMappingURL=security-policy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-policy.js","sourceRoot":"","sources":["../src/security-policy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"security-policy.js","sourceRoot":"","sources":["../src/security-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAc,MAAM,mBAAmB,CAAA;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAyClE;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAmB,EAAE,CAAC,CAAC;IAC1D,MAAM,EAAE,oBAAoB;IAC5B,uBAAuB,EAAE,EAAE;IAC3B,sBAAsB,EAAE,CAAC;IACzB,mBAAmB,EAAE,OAAO;IAC5B,2BAA2B,EAAE,EAAE;CAChC,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAuC,aAAa,CAAC;IAC9E,IAAI,EAAE,mCAAmC;IACzC,QAAQ,EAAE,WAAW;IACrB,OAAO,EAAE,GAAG,EAAE,CAAC,qBAAqB,EAAE;CACvC,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furystack/security",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Security, password management and authorization related stuff for FuryStack",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"url": "https://github.com/furystack/furystack/issues"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@furystack/core": "^
|
|
41
|
-
"@furystack/inject": "^
|
|
42
|
-
"@furystack/repository": "^
|
|
40
|
+
"@furystack/core": "^17.0.0",
|
|
41
|
+
"@furystack/inject": "^13.0.0",
|
|
42
|
+
"@furystack/repository": "^11.0.0"
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://github.com/furystack/furystack",
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@furystack/utils": "^
|
|
46
|
+
"@furystack/utils": "^9.0.0",
|
|
47
47
|
"@types/node": "^25.6.0",
|
|
48
48
|
"typescript": "^6.0.3",
|
|
49
49
|
"vitest": "^4.1.4"
|