@kurdel/auth 0.1.0-beta.2 → 0.1.0-beta.4
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/README.md +63 -8
- package/lib/domain/auth-event.d.ts +3 -0
- package/lib/domain/auth-event.js.map +1 -1
- package/lib/domain/authorization-policy-composition.d.ts +7 -0
- package/lib/domain/authorization-policy-composition.js +45 -0
- package/lib/domain/authorization-policy-composition.js.map +1 -0
- package/lib/domain/authorization-policy.d.ts +9 -1
- package/lib/domain/authorization-policy.js +4 -1
- package/lib/domain/authorization-policy.js.map +1 -1
- package/lib/domain/index.d.ts +2 -0
- package/lib/domain/index.js +2 -0
- package/lib/domain/index.js.map +1 -1
- package/lib/domain/permission.d.ts +6 -0
- package/lib/domain/permission.js +13 -0
- package/lib/domain/permission.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/password/index.d.ts +3 -0
- package/lib/password/index.js +4 -0
- package/lib/password/index.js.map +1 -0
- package/lib/password/password-authentication-service.d.ts +11 -0
- package/lib/password/password-authentication-service.js +19 -0
- package/lib/password/password-authentication-service.js.map +1 -0
- package/lib/password/password-hasher.d.ts +4 -0
- package/lib/password/password-hasher.js +2 -0
- package/lib/password/password-hasher.js.map +1 -0
- package/lib/password/scrypt-password-hasher.d.ts +21 -0
- package/lib/password/scrypt-password-hasher.js +66 -0
- package/lib/password/scrypt-password-hasher.js.map +1 -0
- package/lib/repositories/index.d.ts +1 -0
- package/lib/repositories/index.js +1 -0
- package/lib/repositories/index.js.map +1 -1
- package/lib/repositories/jwt/index.d.ts +1 -0
- package/lib/repositories/jwt/index.js +1 -0
- package/lib/repositories/jwt/index.js.map +1 -1
- package/lib/repositories/jwt/jwt-session-repository.d.ts +10 -0
- package/lib/repositories/jwt/jwt-session-repository.js +2 -0
- package/lib/repositories/jwt/jwt-session-repository.js.map +1 -0
- package/lib/repositories/password/index.d.ts +1 -0
- package/lib/repositories/password/index.js +2 -0
- package/lib/repositories/password/index.js.map +1 -0
- package/lib/repositories/password/password-credential-repository.d.ts +8 -0
- package/lib/repositories/password/password-credential-repository.js +2 -0
- package/lib/repositories/password/password-credential-repository.js.map +1 -0
- package/lib/runtime/create-auth-middleware.js +4 -2
- package/lib/runtime/create-auth-middleware.js.map +1 -1
- package/lib/strategies/jwt/jwt-strategy.d.ts +4 -1
- package/lib/strategies/jwt/jwt-strategy.js +14 -0
- package/lib/strategies/jwt/jwt-strategy.js.map +1 -1
- package/lib/tokens.d.ts +3 -0
- package/lib/tokens.js +3 -0
- package/lib/tokens.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -97,7 +97,9 @@ expressed by roles alone. Register them alongside strategies:
|
|
|
97
97
|
|
|
98
98
|
```ts
|
|
99
99
|
const auth = new AuthModule({
|
|
100
|
-
strategies: [
|
|
100
|
+
strategies: [
|
|
101
|
+
/* ... */
|
|
102
|
+
],
|
|
101
103
|
policies: [
|
|
102
104
|
{
|
|
103
105
|
name: 'manage-users',
|
|
@@ -126,14 +128,43 @@ route({
|
|
|
126
128
|
```
|
|
127
129
|
|
|
128
130
|
A policy receives the complete `AuthContext` and current `HttpContext`, and may
|
|
129
|
-
return a boolean or a promise.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
return a boolean, an `AuthorizationDecision`, or a promise. Decisions can carry
|
|
132
|
+
a safe reason code that is included in authorization-denied events. When
|
|
133
|
+
several policies are listed, every policy must grant access. Policies and
|
|
134
|
+
`roles` may be combined; both checks must then succeed. A rejected policy
|
|
135
|
+
returns `403 Forbidden`, while an unknown policy is reported as an application
|
|
136
|
+
configuration error.
|
|
133
137
|
|
|
134
138
|
Policy providers also support `useFactory`, allowing policies to resolve
|
|
135
139
|
application services from the dependency container.
|
|
136
140
|
|
|
141
|
+
For role-permission models, strategies may resolve `AuthUser.permissions` and
|
|
142
|
+
policies can use the built-in helpers:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { allOf, anyOf, permissionPolicy } from '@kurdel/auth';
|
|
146
|
+
|
|
147
|
+
const apiKeyOnly = {
|
|
148
|
+
authorize: auth => auth.credential?.type === 'api-key'
|
|
149
|
+
? { allowed: true }
|
|
150
|
+
: { allowed: false, reason: 'api-key-required' },
|
|
151
|
+
};
|
|
152
|
+
const ownsRequestedUser = {
|
|
153
|
+
authorize: (auth, ctx) => String(auth.user.id) === ctx.params.id
|
|
154
|
+
? { allowed: true }
|
|
155
|
+
: { allowed: false, reason: 'self-access-required' },
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const manageUsers = allOf(apiKeyOnly, permissionPolicy('users.manage'));
|
|
159
|
+
const viewUser = anyOf(permissionPolicy('users.view.any'), ownsRequestedUser);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Permissions express reusable capabilities, while policies remain executable
|
|
163
|
+
rules that may also inspect the request, credential, or target resource.
|
|
164
|
+
`allOf`, `anyOf`, and `not` compose policies and short-circuit evaluation while
|
|
165
|
+
preserving a nested denial reason for diagnostics. Boolean policies remain
|
|
166
|
+
fully supported.
|
|
167
|
+
|
|
137
168
|
## Security events
|
|
138
169
|
|
|
139
170
|
Configure an event sink to observe sanitized authentication and authorization
|
|
@@ -144,14 +175,17 @@ new AuthModule({
|
|
|
144
175
|
events: {
|
|
145
176
|
useFactory: ioc => ioc.get(APP_TOKENS.AuthEventSink),
|
|
146
177
|
},
|
|
147
|
-
strategies: [
|
|
178
|
+
strategies: [
|
|
179
|
+
/* ... */
|
|
180
|
+
],
|
|
148
181
|
});
|
|
149
182
|
```
|
|
150
183
|
|
|
151
184
|
The package reports successful and failed authentication plus authorization
|
|
152
185
|
denials. API-key management services may additionally report credential issue
|
|
153
186
|
and revocation events. Events contain timestamps, strategy names, user and
|
|
154
|
-
credential identifiers, safe reason codes,
|
|
187
|
+
credential identifiers, safe reason codes, policy names, and optional policy
|
|
188
|
+
decision reasons. Raw API keys,
|
|
155
189
|
JWTs, credential hashes, headers, and request bodies are never part of the
|
|
156
190
|
event contract.
|
|
157
191
|
|
|
@@ -177,7 +211,8 @@ interface AuthContext {
|
|
|
177
211
|
}
|
|
178
212
|
```
|
|
179
213
|
|
|
180
|
-
- `user` is the current application identity
|
|
214
|
+
- `user` is the current application identity, its current roles, and optional
|
|
215
|
+
resolved permissions.
|
|
181
216
|
- `strategy` is the registered name selected by route metadata.
|
|
182
217
|
- `credential` identifies the credential kind and, when available, its stable
|
|
183
218
|
identifier. It never contains the raw API key or JWT.
|
|
@@ -191,6 +226,19 @@ the repository credential ID when one exists. The JWT strategy exposes
|
|
|
191
226
|
`credential.type` as `jwt`, uses the `jti` claim as its optional ID, and places
|
|
192
227
|
the verified payload in `claims`.
|
|
193
228
|
|
|
229
|
+
To make JWTs revocable before their cryptographic expiration, configure a
|
|
230
|
+
`JwtSessionRepository` on the strategy. Session-backed JWTs must contain a
|
|
231
|
+
`jti`; authentication then verifies that the referenced session exists, belongs
|
|
232
|
+
to the token subject, has not been revoked, and has not expired:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
new JwtStrategy(jwtService, users, {
|
|
236
|
+
sessions: jwtSessions,
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Without `sessions`, JWT verification remains stateless and backward compatible.
|
|
241
|
+
|
|
194
242
|
## User and credential repositories
|
|
195
243
|
|
|
196
244
|
Authentication strategies do not own user data. Both built-in strategies load
|
|
@@ -213,6 +261,13 @@ credentials with stable IDs. The strategy records usage only after the key is
|
|
|
213
261
|
accepted and its current user is resolved; rejected, expired, revoked, and
|
|
214
262
|
orphaned credentials are never recorded.
|
|
215
263
|
|
|
264
|
+
Password login is composed from `PasswordAuthenticationService`, a
|
|
265
|
+
`PasswordCredentialRepository`, and a `PasswordHasher`. The built-in
|
|
266
|
+
`ScryptPasswordHasher` stores a random salt and its work parameters in a
|
|
267
|
+
self-describing encoded value. The service returns the current `AuthUser` only
|
|
268
|
+
after both the password and user state have been verified; unknown logins and
|
|
269
|
+
invalid passwords both return `null`.
|
|
270
|
+
|
|
216
271
|
For database-backed implementations, use `@kurdel/auth-db`. Applications may
|
|
217
272
|
also implement these interfaces for another database, an external identity
|
|
218
273
|
service, or an in-memory test setup.
|
|
@@ -16,8 +16,11 @@ export type AuthEvent = (AuthEventBase & {
|
|
|
16
16
|
strategy?: string;
|
|
17
17
|
reason: 'missing-role' | 'missing-authentication' | 'policy-rejected';
|
|
18
18
|
policy?: string;
|
|
19
|
+
decisionReason?: string;
|
|
19
20
|
}) | (AuthEventBase & {
|
|
20
21
|
type: 'api-key.issued' | 'api-key.revoked';
|
|
22
|
+
}) | (AuthEventBase & {
|
|
23
|
+
type: 'jwt-session.created' | 'jwt-session.revoked';
|
|
21
24
|
});
|
|
22
25
|
/** Receives sanitized authentication and authorization lifecycle events. */
|
|
23
26
|
export interface AuthEventSink {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-event.js","sourceRoot":"","sources":["../../src/domain/auth-event.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth-event.js","sourceRoot":"","sources":["../../src/domain/auth-event.ts"],"names":[],"mappings":"AAqCA,gFAAgF;AAChF,MAAM,OAAO,iBAAiB;IAC5B,MAAM,CAAC,MAAiB,IAAS,CAAC;CACnC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AuthorizationPolicy } from './authorization-policy.js';
|
|
2
|
+
/** Grants access only when every nested policy grants access. */
|
|
3
|
+
export declare function allOf(...policies: AuthorizationPolicy[]): AuthorizationPolicy;
|
|
4
|
+
/** Grants access when at least one nested policy grants access. */
|
|
5
|
+
export declare function anyOf(...policies: AuthorizationPolicy[]): AuthorizationPolicy;
|
|
6
|
+
/** Inverts a nested policy and uses the supplied reason when inversion denies access. */
|
|
7
|
+
export declare function not(policy: AuthorizationPolicy, reason?: string): AuthorizationPolicy;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { authorizationDecision, } from './authorization-policy.js';
|
|
2
|
+
async function evaluate(policy, auth, ctx) {
|
|
3
|
+
return authorizationDecision(await policy.authorize(auth, ctx));
|
|
4
|
+
}
|
|
5
|
+
/** Grants access only when every nested policy grants access. */
|
|
6
|
+
export function allOf(...policies) {
|
|
7
|
+
return {
|
|
8
|
+
async authorize(auth, ctx) {
|
|
9
|
+
for (const policy of policies) {
|
|
10
|
+
const decision = await evaluate(policy, auth, ctx);
|
|
11
|
+
if (!decision.allowed)
|
|
12
|
+
return decision;
|
|
13
|
+
}
|
|
14
|
+
return { allowed: true };
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Grants access when at least one nested policy grants access. */
|
|
19
|
+
export function anyOf(...policies) {
|
|
20
|
+
return {
|
|
21
|
+
async authorize(auth, ctx) {
|
|
22
|
+
let denied = { allowed: false };
|
|
23
|
+
for (const policy of policies) {
|
|
24
|
+
const decision = await evaluate(policy, auth, ctx);
|
|
25
|
+
if (decision.allowed)
|
|
26
|
+
return decision;
|
|
27
|
+
if (decision.reason)
|
|
28
|
+
denied = decision;
|
|
29
|
+
}
|
|
30
|
+
return denied;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Inverts a nested policy and uses the supplied reason when inversion denies access. */
|
|
35
|
+
export function not(policy, reason) {
|
|
36
|
+
return {
|
|
37
|
+
async authorize(auth, ctx) {
|
|
38
|
+
const decision = await evaluate(policy, auth, ctx);
|
|
39
|
+
return decision.allowed
|
|
40
|
+
? { allowed: false, ...(reason ? { reason } : {}) }
|
|
41
|
+
: { allowed: true };
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=authorization-policy-composition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorization-policy-composition.js","sourceRoot":"","sources":["../../src/domain/authorization-policy-composition.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,qBAAqB,GAGtB,MAAM,2BAA2B,CAAC;AAEnC,KAAK,UAAU,QAAQ,CACrB,MAA2B,EAC3B,IAA2B,EAC3B,GAAgB;IAEhB,OAAO,qBAAqB,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,KAAK,CAAC,GAAG,QAA+B;IACtD,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG;YACvB,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;gBACnD,IAAI,CAAC,QAAQ,CAAC,OAAO;oBAAE,OAAO,QAAQ,CAAC;YACzC,CAAC;YACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;KACF,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,KAAK,CAAC,GAAG,QAA+B;IACtD,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG;YACvB,IAAI,MAAM,GAA0B,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;YACvD,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;gBAC9B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;gBACnD,IAAI,QAAQ,CAAC,OAAO;oBAAE,OAAO,QAAQ,CAAC;gBACtC,IAAI,QAAQ,CAAC,MAAM;oBAAE,MAAM,GAAG,QAAQ,CAAC;YACzC,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,GAAG,CAAC,MAA2B,EAAE,MAAe;IAC9D,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG;YACvB,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACnD,OAAO,QAAQ,CAAC,OAAO;gBACrB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;gBACnD,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { AuthContext } from '@kurdel/common';
|
|
2
2
|
import type { HttpContext } from '@kurdel/core/http';
|
|
3
|
+
export type AuthorizationDecision = {
|
|
4
|
+
allowed: boolean;
|
|
5
|
+
/** Safe, non-sensitive reason code suitable for authorization audit events. */
|
|
6
|
+
reason?: string;
|
|
7
|
+
};
|
|
8
|
+
export type AuthorizationPolicyResult = boolean | AuthorizationDecision;
|
|
3
9
|
/** Performs an application-specific authorization check for a request. */
|
|
4
10
|
export interface AuthorizationPolicy {
|
|
5
|
-
authorize(auth: Readonly<AuthContext>, ctx: HttpContext):
|
|
11
|
+
authorize(auth: Readonly<AuthContext>, ctx: HttpContext): AuthorizationPolicyResult | Promise<AuthorizationPolicyResult>;
|
|
6
12
|
}
|
|
13
|
+
/** Converts a boolean-compatible policy result into a diagnostic decision. */
|
|
14
|
+
export declare function authorizationDecision(result: AuthorizationPolicyResult): AuthorizationDecision;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-policy.js","sourceRoot":"","sources":["../../src/domain/authorization-policy.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"authorization-policy.js","sourceRoot":"","sources":["../../src/domain/authorization-policy.ts"],"names":[],"mappings":"AAmBA,8EAA8E;AAC9E,MAAM,UAAU,qBAAqB,CAAC,MAAiC;IACrE,OAAO,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AACpE,CAAC"}
|
package/lib/domain/index.d.ts
CHANGED
|
@@ -4,4 +4,6 @@ export * from './auth-event-sink-provider.js';
|
|
|
4
4
|
export * from './auth-strategy-provider.js';
|
|
5
5
|
export * from './auth-strategy.js';
|
|
6
6
|
export * from './authorization-policy.js';
|
|
7
|
+
export * from './authorization-policy-composition.js';
|
|
7
8
|
export * from './authorization-policy-provider.js';
|
|
9
|
+
export * from './permission.js';
|
package/lib/domain/index.js
CHANGED
|
@@ -4,5 +4,7 @@ export * from './auth-event-sink-provider.js';
|
|
|
4
4
|
export * from './auth-strategy-provider.js';
|
|
5
5
|
export * from './auth-strategy.js';
|
|
6
6
|
export * from './authorization-policy.js';
|
|
7
|
+
export * from './authorization-policy-composition.js';
|
|
7
8
|
export * from './authorization-policy-provider.js';
|
|
9
|
+
export * from './permission.js';
|
|
8
10
|
//# sourceMappingURL=index.js.map
|
package/lib/domain/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/domain/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oCAAoC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/domain/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uCAAuC,CAAC;AACtD,cAAc,oCAAoC,CAAC;AACnD,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AuthUser } from '@kurdel/common';
|
|
2
|
+
import type { AuthorizationPolicy } from './authorization-policy.js';
|
|
3
|
+
/** Returns whether an authenticated user has a resolved capability. */
|
|
4
|
+
export declare function hasPermission(user: Readonly<AuthUser>, permission: string): boolean;
|
|
5
|
+
/** Creates a policy that requires one resolved permission. */
|
|
6
|
+
export declare function permissionPolicy(permission: string): AuthorizationPolicy;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Returns whether an authenticated user has a resolved capability. */
|
|
2
|
+
export function hasPermission(user, permission) {
|
|
3
|
+
return user.permissions?.includes(permission) ?? false;
|
|
4
|
+
}
|
|
5
|
+
/** Creates a policy that requires one resolved permission. */
|
|
6
|
+
export function permissionPolicy(permission) {
|
|
7
|
+
return {
|
|
8
|
+
authorize: auth => hasPermission(auth.user, permission)
|
|
9
|
+
? { allowed: true }
|
|
10
|
+
: { allowed: false, reason: `missing-permission:${permission}` },
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=permission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.js","sourceRoot":"","sources":["../../src/domain/permission.ts"],"names":[],"mappings":"AAIA,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAC,IAAwB,EAAE,UAAkB;IACxE,OAAO,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC;AACzD,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC;YACrD,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE;YACnB,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,UAAU,EAAE,EAAE;KACnE,CAAC;AACJ,CAAC"}
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/password/index.ts"],"names":[],"mappings":"AAAA,cAAc,sCAAsC,CAAC;AACrD,cAAc,sBAAsB,CAAC;AACrC,cAAc,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AuthUser } from '@kurdel/common';
|
|
2
|
+
import type { PasswordCredentialRepository } from '../repositories/password/index.js';
|
|
3
|
+
import type { AuthUserRepository } from '../repositories/user/index.js';
|
|
4
|
+
import type { PasswordHasher } from './password-hasher.js';
|
|
5
|
+
export declare class PasswordAuthenticationService {
|
|
6
|
+
private readonly credentials;
|
|
7
|
+
private readonly users;
|
|
8
|
+
private readonly hasher;
|
|
9
|
+
constructor(credentials: PasswordCredentialRepository, users: AuthUserRepository, hasher: PasswordHasher);
|
|
10
|
+
authenticate(login: string, password: string): Promise<AuthUser | null>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class PasswordAuthenticationService {
|
|
2
|
+
constructor(credentials, users, hasher) {
|
|
3
|
+
this.credentials = credentials;
|
|
4
|
+
this.users = users;
|
|
5
|
+
this.hasher = hasher;
|
|
6
|
+
}
|
|
7
|
+
async authenticate(login, password) {
|
|
8
|
+
const credential = await this.credentials.findByLogin(login);
|
|
9
|
+
if (!credential) {
|
|
10
|
+
// Keep the expensive password operation on the unknown-login path too.
|
|
11
|
+
await this.hasher.hash(password);
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
if (!(await this.hasher.verify(password, credential.passwordHash)))
|
|
15
|
+
return null;
|
|
16
|
+
return this.users.findById(credential.userId);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=password-authentication-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-authentication-service.js","sourceRoot":"","sources":["../../src/password/password-authentication-service.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,6BAA6B;IACxC,YACmB,WAAyC,EACzC,KAAyB,EACzB,MAAsB;QAFtB,gBAAW,GAAX,WAAW,CAA8B;QACzC,UAAK,GAAL,KAAK,CAAoB;QACzB,WAAM,GAAN,MAAM,CAAgB;IACtC,CAAC;IAEJ,KAAK,CAAC,YAAY,CAAC,KAAa,EAAE,QAAgB;QAChD,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,uEAAuE;YACvE,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACjC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAChF,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-hasher.js","sourceRoot":"","sources":["../../src/password/password-hasher.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PasswordHasher } from './password-hasher.js';
|
|
2
|
+
export interface ScryptPasswordHasherOptions {
|
|
3
|
+
cost?: number;
|
|
4
|
+
blockSize?: number;
|
|
5
|
+
parallelization?: number;
|
|
6
|
+
keyLength?: number;
|
|
7
|
+
saltLength?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Password hasher with a self-describing format that supports future rehashing. */
|
|
10
|
+
export declare class ScryptPasswordHasher implements PasswordHasher {
|
|
11
|
+
private readonly cost;
|
|
12
|
+
private readonly blockSize;
|
|
13
|
+
private readonly parallelization;
|
|
14
|
+
private readonly keyLength;
|
|
15
|
+
private readonly saltLength;
|
|
16
|
+
constructor(options?: ScryptPasswordHasherOptions);
|
|
17
|
+
hash(password: string): Promise<string>;
|
|
18
|
+
verify(password: string, encodedHash: string): Promise<boolean>;
|
|
19
|
+
private derive;
|
|
20
|
+
private validParameters;
|
|
21
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
/** Password hasher with a self-describing format that supports future rehashing. */
|
|
3
|
+
export class ScryptPasswordHasher {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.cost = options.cost ?? 16384;
|
|
6
|
+
this.blockSize = options.blockSize ?? 8;
|
|
7
|
+
this.parallelization = options.parallelization ?? 1;
|
|
8
|
+
this.keyLength = options.keyLength ?? 64;
|
|
9
|
+
this.saltLength = options.saltLength ?? 16;
|
|
10
|
+
}
|
|
11
|
+
async hash(password) {
|
|
12
|
+
const salt = crypto.randomBytes(this.saltLength);
|
|
13
|
+
const derived = await this.derive(password, salt, this.cost, this.blockSize, this.parallelization, this.keyLength);
|
|
14
|
+
return [
|
|
15
|
+
'scrypt',
|
|
16
|
+
this.cost,
|
|
17
|
+
this.blockSize,
|
|
18
|
+
this.parallelization,
|
|
19
|
+
salt.toString('base64url'),
|
|
20
|
+
derived.toString('base64url'),
|
|
21
|
+
].join('$');
|
|
22
|
+
}
|
|
23
|
+
async verify(password, encodedHash) {
|
|
24
|
+
const parts = encodedHash.split('$');
|
|
25
|
+
if (parts.length !== 6 || parts[0] !== 'scrypt')
|
|
26
|
+
return false;
|
|
27
|
+
const [cost, blockSize, parallelization] = parts.slice(1, 4).map(Number);
|
|
28
|
+
const salt = Buffer.from(parts[4], 'base64url');
|
|
29
|
+
const expected = Buffer.from(parts[5], 'base64url');
|
|
30
|
+
if (!this.validParameters(cost, blockSize, parallelization, salt, expected))
|
|
31
|
+
return false;
|
|
32
|
+
try {
|
|
33
|
+
const actual = await this.derive(password, salt, cost, blockSize, parallelization, expected.length);
|
|
34
|
+
return crypto.timingSafeEqual(actual, expected);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async derive(password, salt, cost, blockSize, parallelization, keyLength) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
crypto.scrypt(password, salt, keyLength, {
|
|
43
|
+
N: cost,
|
|
44
|
+
r: blockSize,
|
|
45
|
+
p: parallelization,
|
|
46
|
+
maxmem: Math.max(32 * 1024 * 1024, 256 * cost * blockSize),
|
|
47
|
+
}, (error, derivedKey) => (error ? reject(error) : resolve(derivedKey)));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
validParameters(cost, blockSize, parallelization, salt, hash) {
|
|
51
|
+
return (Number.isInteger(cost) &&
|
|
52
|
+
cost >= 1024 &&
|
|
53
|
+
cost <= 1048576 &&
|
|
54
|
+
Number.isInteger(blockSize) &&
|
|
55
|
+
blockSize >= 1 &&
|
|
56
|
+
blockSize <= 32 &&
|
|
57
|
+
Number.isInteger(parallelization) &&
|
|
58
|
+
parallelization >= 1 &&
|
|
59
|
+
parallelization <= 16 &&
|
|
60
|
+
salt.length >= 8 &&
|
|
61
|
+
salt.length <= 64 &&
|
|
62
|
+
hash.length >= 32 &&
|
|
63
|
+
hash.length <= 128);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=scrypt-password-hasher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrypt-password-hasher.js","sourceRoot":"","sources":["../../src/password/scrypt-password-hasher.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/repositories/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/repositories/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/repositories/jwt/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/repositories/jwt/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type JwtSession = {
|
|
2
|
+
id: string;
|
|
3
|
+
userId: string | number;
|
|
4
|
+
revoked: boolean;
|
|
5
|
+
expiresAt?: Date;
|
|
6
|
+
};
|
|
7
|
+
/** Resolves server-side JWT session state used for token revocation. */
|
|
8
|
+
export interface JwtSessionRepository {
|
|
9
|
+
findById(id: string): Promise<JwtSession | null>;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-session-repository.js","sourceRoot":"","sources":["../../../src/repositories/jwt/jwt-session-repository.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './password-credential-repository.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/repositories/password/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PasswordCredential {
|
|
2
|
+
userId: string | number;
|
|
3
|
+
passwordHash: string;
|
|
4
|
+
}
|
|
5
|
+
/** Resolves a password credential by an application-defined login. */
|
|
6
|
+
export interface PasswordCredentialRepository {
|
|
7
|
+
findByLogin(login: string): Promise<PasswordCredential | null> | PasswordCredential | null;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-credential-repository.js","sourceRoot":"","sources":["../../../src/repositories/password/password-credential-repository.ts"],"names":[],"mappings":""}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NoopAuthEventSink } from '../domain/index.js';
|
|
1
|
+
import { authorizationDecision, NoopAuthEventSink, } from '../domain/index.js';
|
|
2
2
|
import { AuthorizationPolicyRegistry } from './authorization-policy-registry.js';
|
|
3
3
|
export function createAuthMiddleware(registry, policies = new AuthorizationPolicyRegistry(), events = new NoopAuthEventSink(), now = () => new Date()) {
|
|
4
4
|
return async (ctx, next) => {
|
|
@@ -69,7 +69,8 @@ export function createAuthMiddleware(registry, policies = new AuthorizationPolic
|
|
|
69
69
|
if (!policy) {
|
|
70
70
|
return ctx.json(500, { error: `Unknown authorization policy '${name}'` });
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
const decision = authorizationDecision(await policy.authorize(auth, ctx));
|
|
73
|
+
if (!decision.allowed) {
|
|
73
74
|
await events.report({
|
|
74
75
|
type: 'authorization.denied',
|
|
75
76
|
occurredAt: now(),
|
|
@@ -78,6 +79,7 @@ export function createAuthMiddleware(registry, policies = new AuthorizationPolic
|
|
|
78
79
|
...(auth.credential ? { credential: auth.credential } : {}),
|
|
79
80
|
reason: 'policy-rejected',
|
|
80
81
|
policy: name,
|
|
82
|
+
...(decision.reason ? { decisionReason: decision.reason } : {}),
|
|
81
83
|
});
|
|
82
84
|
return ctx.json(403, { error: 'Forbidden' });
|
|
83
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-auth-middleware.js","sourceRoot":"","sources":["../../src/runtime/create-auth-middleware.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"create-auth-middleware.js","sourceRoot":"","sources":["../../src/runtime/create-auth-middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HttpRequest } from '@kurdel/common';
|
|
2
2
|
import type { AuthenticationResult, AuthStrategy } from '../../domain/index.js';
|
|
3
|
-
import type { AuthUserRepository } from '../../repositories/index.js';
|
|
3
|
+
import type { AuthUserRepository, JwtSessionRepository } from '../../repositories/index.js';
|
|
4
4
|
import type { JwtService } from '../../strategies/index.js';
|
|
5
5
|
/**
|
|
6
6
|
* ## JwtStrategyOptions
|
|
@@ -10,6 +10,8 @@ import type { JwtService } from '../../strategies/index.js';
|
|
|
10
10
|
export interface JwtStrategyOptions {
|
|
11
11
|
header?: string;
|
|
12
12
|
prefix?: string;
|
|
13
|
+
/** When configured, every token must reference an active session through `jti`. */
|
|
14
|
+
sessions?: JwtSessionRepository;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* ## JwtStrategy
|
|
@@ -22,6 +24,7 @@ export declare class JwtStrategy implements AuthStrategy {
|
|
|
22
24
|
private readonly users;
|
|
23
25
|
private readonly header;
|
|
24
26
|
private readonly prefix;
|
|
27
|
+
private readonly sessions?;
|
|
25
28
|
constructor(service: JwtService, users: AuthUserRepository, opts?: JwtStrategyOptions);
|
|
26
29
|
authenticate(req: HttpRequest): Promise<AuthenticationResult | null>;
|
|
27
30
|
}
|
|
@@ -10,6 +10,7 @@ export class JwtStrategy {
|
|
|
10
10
|
this.users = users;
|
|
11
11
|
this.header = (opts.header ?? 'authorization').toLowerCase();
|
|
12
12
|
this.prefix = (opts.prefix ?? 'Bearer').toLowerCase();
|
|
13
|
+
this.sessions = opts.sessions;
|
|
13
14
|
}
|
|
14
15
|
async authenticate(req) {
|
|
15
16
|
const raw = req.headers?.[this.header];
|
|
@@ -25,6 +26,19 @@ export class JwtStrategy {
|
|
|
25
26
|
const payload = this.service.verify(token);
|
|
26
27
|
if (!payload.sub)
|
|
27
28
|
return null;
|
|
29
|
+
if (this.sessions) {
|
|
30
|
+
if (typeof payload.jti !== 'string')
|
|
31
|
+
return null;
|
|
32
|
+
const session = await this.sessions.findById(payload.jti);
|
|
33
|
+
if (!session || session.revoked || String(session.userId) !== String(payload.sub)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (session.expiresAt) {
|
|
37
|
+
const expiresAt = session.expiresAt.getTime();
|
|
38
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now())
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
28
42
|
const user = await this.users.findById(payload.sub);
|
|
29
43
|
if (!user)
|
|
30
44
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt-strategy.js","sourceRoot":"","sources":["../../../src/strategies/jwt/jwt-strategy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"jwt-strategy.js","sourceRoot":"","sources":["../../../src/strategies/jwt/jwt-strategy.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IAKtB,YACmB,OAAmB,EACnB,KAAyB,EAC1C,OAA2B,EAAE;QAFZ,YAAO,GAAP,OAAO,CAAY;QACnB,UAAK,GAAL,KAAK,CAAoB;QAG1C,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7D,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;QACtD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,GAAgB;QACjC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QAEtB,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QAC9C,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAExD,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAErD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAE3C,IAAI,CAAC,OAAO,CAAC,GAAG;gBAAE,OAAO,IAAI,CAAC;YAE9B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;oBAAE,OAAO,IAAI,CAAC;gBACjD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAC1D,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;oBAClF,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;oBACtB,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;oBAC9C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE;wBAAE,OAAO,IAAI,CAAC;gBAC1E,CAAC;YACH,CAAC;YAED,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACpD,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YAEvB,OAAO;gBACL,IAAI;gBACJ,UAAU,EAAE;oBACV,IAAI,EAAE,KAAK;oBACX,GAAG,CAAC,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAChE;gBACD,MAAM,EAAE,OAAO;aAChB,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF"}
|
package/lib/tokens.d.ts
CHANGED
package/lib/tokens.js
CHANGED
|
@@ -8,6 +8,9 @@ export const AUTH_TOKENS = {
|
|
|
8
8
|
JwtRepository: UserRepository,
|
|
9
9
|
ApiKeyRepository: Symbol('ApiKeyRepository'),
|
|
10
10
|
ApiKeyUsageRecorder: Symbol('ApiKeyUsageRecorder'),
|
|
11
|
+
JwtSessionRepository: Symbol('JwtSessionRepository'),
|
|
12
|
+
PasswordCredentialRepository: Symbol('PasswordCredentialRepository'),
|
|
13
|
+
PasswordAuthenticationService: Symbol('PasswordAuthenticationService'),
|
|
11
14
|
JwtService: Symbol('JwtService'),
|
|
12
15
|
};
|
|
13
16
|
//# sourceMappingURL=tokens.js.map
|
package/lib/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAEpD,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,gBAAgB,EAAE,MAAM,CAAC,sBAAsB,CAAC;IAChD,cAAc,EAAE,MAAM,CAAC,6BAA6B,CAAC;IACrD,SAAS,EAAE,MAAM,CAAC,eAAe,CAAC;IAClC,cAAc;IACd,sCAAsC;IACtC,aAAa,EAAE,cAAc;IAC7B,gBAAgB,EAAE,MAAM,CAAC,kBAAkB,CAAC;IAC5C,mBAAmB,EAAE,MAAM,CAAC,qBAAqB,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC;CACjC,CAAC"}
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,MAAM,cAAc,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAEpD,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,gBAAgB,EAAE,MAAM,CAAC,sBAAsB,CAAC;IAChD,cAAc,EAAE,MAAM,CAAC,6BAA6B,CAAC;IACrD,SAAS,EAAE,MAAM,CAAC,eAAe,CAAC;IAClC,cAAc;IACd,sCAAsC;IACtC,aAAa,EAAE,cAAc;IAC7B,gBAAgB,EAAE,MAAM,CAAC,kBAAkB,CAAC;IAC5C,mBAAmB,EAAE,MAAM,CAAC,qBAAqB,CAAC;IAClD,oBAAoB,EAAE,MAAM,CAAC,sBAAsB,CAAC;IACpD,4BAA4B,EAAE,MAAM,CAAC,8BAA8B,CAAC;IACpE,6BAA6B,EAAE,MAAM,CAAC,+BAA+B,CAAC;IACtE,UAAU,EAAE,MAAM,CAAC,YAAY,CAAC;CACjC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kurdel/auth",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "Authentication and authorization primitives for Kurdel applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"tsc-alias": "^1.8.16"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@kurdel/common": "0.1.0-beta.
|
|
53
|
-
"@kurdel/core": "0.1.0-beta.
|
|
54
|
-
"@kurdel/ioc": "0.1.0-beta.
|
|
52
|
+
"@kurdel/common": "0.1.0-beta.4",
|
|
53
|
+
"@kurdel/core": "0.1.0-beta.4",
|
|
54
|
+
"@kurdel/ioc": "0.1.0-beta.4"
|
|
55
55
|
}
|
|
56
56
|
}
|