@furystack/rest-service 11.0.7 → 12.1.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 +120 -0
- package/README.md +3 -2
- package/esm/actions/index.d.ts +1 -0
- package/esm/actions/index.d.ts.map +1 -1
- package/esm/actions/index.js +1 -0
- package/esm/actions/index.js.map +1 -1
- package/esm/actions/login.d.ts +7 -3
- package/esm/actions/login.d.ts.map +1 -1
- package/esm/actions/login.js +11 -5
- package/esm/actions/login.js.map +1 -1
- package/esm/actions/password-login-action.d.ts +24 -0
- package/esm/actions/password-login-action.d.ts.map +1 -0
- package/esm/actions/password-login-action.js +31 -0
- package/esm/actions/password-login-action.js.map +1 -0
- package/esm/actions/password-login-action.spec.d.ts +2 -0
- package/esm/actions/password-login-action.spec.d.ts.map +1 -0
- package/esm/actions/password-login-action.spec.js +105 -0
- package/esm/actions/password-login-action.spec.js.map +1 -0
- package/esm/authenticate.d.ts.map +1 -1
- package/esm/authenticate.js +4 -1
- package/esm/authenticate.js.map +1 -1
- package/esm/authenticate.spec.js +6 -4
- package/esm/authenticate.spec.js.map +1 -1
- package/esm/authentication-providers/authentication-provider.d.ts +25 -0
- package/esm/authentication-providers/authentication-provider.d.ts.map +1 -0
- package/esm/authentication-providers/authentication-provider.js +2 -0
- package/esm/authentication-providers/authentication-provider.js.map +1 -0
- package/esm/authentication-providers/basic-auth-provider.d.ts +8 -0
- package/esm/authentication-providers/basic-auth-provider.d.ts.map +1 -0
- package/esm/authentication-providers/basic-auth-provider.js +18 -0
- package/esm/authentication-providers/basic-auth-provider.js.map +1 -0
- package/esm/authentication-providers/cookie-auth-provider.d.ts +11 -0
- package/esm/authentication-providers/cookie-auth-provider.d.ts.map +1 -0
- package/esm/authentication-providers/cookie-auth-provider.js +21 -0
- package/esm/authentication-providers/cookie-auth-provider.js.map +1 -0
- package/esm/authentication-providers/helpers.d.ts +11 -0
- package/esm/authentication-providers/helpers.d.ts.map +1 -0
- package/esm/authentication-providers/helpers.js +47 -0
- package/esm/authentication-providers/helpers.js.map +1 -0
- package/esm/authentication-providers/index.d.ts +5 -0
- package/esm/authentication-providers/index.d.ts.map +1 -0
- package/esm/authentication-providers/index.js +5 -0
- package/esm/authentication-providers/index.js.map +1 -0
- package/esm/endpoint-generators/utils.d.ts.map +1 -1
- package/esm/endpoint-generators/utils.js +4 -1
- package/esm/endpoint-generators/utils.js.map +1 -1
- package/esm/helpers.d.ts +5 -2
- package/esm/helpers.d.ts.map +1 -1
- package/esm/helpers.js +27 -3
- package/esm/helpers.js.map +1 -1
- package/esm/helpers.spec.js +37 -0
- package/esm/helpers.spec.js.map +1 -1
- package/esm/http-authentication-settings.d.ts +11 -4
- package/esm/http-authentication-settings.d.ts.map +1 -1
- package/esm/http-authentication-settings.js +9 -2
- package/esm/http-authentication-settings.js.map +1 -1
- package/esm/http-user-context.d.ts +9 -4
- package/esm/http-user-context.d.ts.map +1 -1
- package/esm/http-user-context.js +28 -55
- package/esm/http-user-context.js.map +1 -1
- package/esm/http-user-context.spec.d.ts +3 -1
- package/esm/http-user-context.spec.d.ts.map +1 -1
- package/esm/http-user-context.spec.js +103 -45
- package/esm/http-user-context.spec.js.map +1 -1
- package/esm/index.d.ts +2 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +2 -0
- package/esm/index.js.map +1 -1
- package/esm/login-response-strategy.d.ts +28 -0
- package/esm/login-response-strategy.d.ts.map +1 -0
- package/esm/login-response-strategy.js +28 -0
- package/esm/login-response-strategy.js.map +1 -0
- package/esm/login-response-strategy.spec.d.ts +2 -0
- package/esm/login-response-strategy.spec.d.ts.map +1 -0
- package/esm/login-response-strategy.spec.js +78 -0
- package/esm/login-response-strategy.spec.js.map +1 -0
- package/esm/rest-service.integration.spec.d.ts.map +1 -1
- package/esm/rest-service.integration.spec.js +5 -4
- package/esm/rest-service.integration.spec.js.map +1 -1
- package/esm/validate.integration.spec.js +5 -0
- package/esm/validate.integration.spec.js.map +1 -1
- package/package.json +7 -7
- package/src/actions/index.ts +1 -0
- package/src/actions/login.ts +12 -6
- package/src/actions/password-login-action.spec.ts +122 -0
- package/src/actions/password-login-action.ts +35 -0
- package/src/authenticate.spec.ts +6 -4
- package/src/authenticate.ts +4 -1
- package/src/authentication-providers/authentication-provider.ts +25 -0
- package/src/authentication-providers/basic-auth-provider.ts +21 -0
- package/src/authentication-providers/cookie-auth-provider.ts +26 -0
- package/src/authentication-providers/helpers.ts +73 -0
- package/src/authentication-providers/index.ts +4 -0
- package/src/endpoint-generators/utils.ts +4 -1
- package/src/helpers.spec.ts +40 -0
- package/src/helpers.ts +48 -3
- package/src/http-authentication-settings.ts +30 -21
- package/src/http-user-context.spec.ts +462 -394
- package/src/http-user-context.ts +164 -194
- package/src/index.ts +2 -0
- package/src/login-response-strategy.spec.ts +90 -0
- package/src/login-response-strategy.ts +48 -0
- package/src/rest-service.integration.spec.ts +5 -4
- package/src/validate.integration.spec.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,125 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [12.1.0] - 2026-02-26
|
|
4
|
+
|
|
5
|
+
### 🔧 Chores
|
|
6
|
+
|
|
7
|
+
- Normalized line endings in `http-user-context.ts`, `http-authentication-settings.ts`, and related spec files
|
|
8
|
+
|
|
9
|
+
### ✨ Features
|
|
10
|
+
|
|
11
|
+
### `LoginResponseStrategy<TResult>` type
|
|
12
|
+
|
|
13
|
+
New pluggable type that decouples login actions from session/token creation. A strategy turns an authenticated `User` into an `ActionResult<TResult>` — the generic parameter flows through to the action's return type for full type inference.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import type { LoginResponseStrategy } from '@furystack/rest-service'
|
|
17
|
+
|
|
18
|
+
type LoginResponseStrategy<TResult> = {
|
|
19
|
+
createLoginResponse: (user: User, injector: Injector) => Promise<ActionResult<TResult>>
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### `createCookieLoginStrategy(injector)`
|
|
24
|
+
|
|
25
|
+
Factory that creates a cookie-based `LoginResponseStrategy<User>`. On login it generates a random session ID, persists it in the session DataSet, and returns the user with a `Set-Cookie` header.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { createCookieLoginStrategy } from '@furystack/rest-service'
|
|
29
|
+
|
|
30
|
+
const cookieStrategy = createCookieLoginStrategy(injector)
|
|
31
|
+
// cookieStrategy.createLoginResponse(user, injector) → ActionResult<User> with Set-Cookie header
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### `createPasswordLoginAction(strategy)`
|
|
35
|
+
|
|
36
|
+
Factory that creates a password-based login `RequestAction`. Authenticates via `HttpUserContext.authenticateUser()` then delegates session/token creation to the provided strategy. Includes timing-attack mitigation on failure.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { createPasswordLoginAction, createCookieLoginStrategy } from '@furystack/rest-service'
|
|
40
|
+
|
|
41
|
+
const cookieStrategy = createCookieLoginStrategy(injector)
|
|
42
|
+
const loginAction = createPasswordLoginAction(cookieStrategy)
|
|
43
|
+
// loginAction: RequestAction<{ result: User; body: { username: string; password: string } }>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 🧪 Tests
|
|
47
|
+
|
|
48
|
+
- Added `login-response-strategy.spec.ts` — tests cookie strategy session creation, Set-Cookie headers, session persistence, and session ID uniqueness
|
|
49
|
+
- Added `password-login-action.spec.ts` — tests strategy delegation, user forwarding, auth failure handling, and custom strategy result types
|
|
50
|
+
|
|
51
|
+
### ⬆️ Dependencies
|
|
52
|
+
|
|
53
|
+
- Bumped `@types/node` from ^25.3.0 to ^25.3.1
|
|
54
|
+
|
|
55
|
+
## [12.0.0] - 2026-02-26
|
|
56
|
+
|
|
57
|
+
### ✨ Features
|
|
58
|
+
|
|
59
|
+
### Pluggable Authentication Provider System
|
|
60
|
+
|
|
61
|
+
Introduced the `AuthenticationProvider` type and refactored `HttpUserContext.authenticateRequest()` to iterate an ordered provider chain instead of hardcoded Basic Auth and Cookie Auth logic.
|
|
62
|
+
|
|
63
|
+
- `AuthenticationProvider` - Type for pluggable authentication providers. Each provider returns `User` on success, `null` if it doesn't apply, or throws on auth failure.
|
|
64
|
+
- `createBasicAuthProvider()` - Factory that extracts the existing Basic Auth logic into a standalone provider
|
|
65
|
+
- `createCookieAuthProvider()` - Factory that extracts the existing Cookie Auth logic into a standalone provider
|
|
66
|
+
- `HttpAuthenticationSettings.authenticationProviders` - Ordered list of providers, populated by `useHttpAuthentication()` and extensible by auth plugins like `useJwtAuthentication()`
|
|
67
|
+
|
|
68
|
+
**Usage:**
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
useHttpAuthentication(injector, {
|
|
72
|
+
enableBasicAuth: true,
|
|
73
|
+
authenticationProviders: [myCustomProvider],
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Custom providers are appended after the built-in Basic Auth and Cookie Auth providers.
|
|
78
|
+
|
|
79
|
+
### ♻️ Refactoring
|
|
80
|
+
|
|
81
|
+
- `useHttpAuthentication()` now eagerly resolves `PasswordAuthenticator` and store dependencies at setup time, constructing providers with resolved instances rather than passing the `Injector`
|
|
82
|
+
- `Authenticate()` middleware checks for a registered `'basic-auth'` provider by name instead of reading the `enableBasicAuth` flag when deciding whether to include the `WWW-Authenticate: Basic` response header
|
|
83
|
+
- Extracted shared store lookup helpers (`authenticateUserWithDataSet`, `findSessionById`, `findUserByName`, `extractSessionIdFromCookies`) into `authentication-providers/helpers.ts`
|
|
84
|
+
|
|
85
|
+
### 💥 Breaking Changes
|
|
86
|
+
|
|
87
|
+
- `HttpAuthenticationSettings.getUserStore(StoreManager)` → `getUserDataSet(Injector)` — now returns a `DataSet` instead of a `PhysicalStore`
|
|
88
|
+
- `HttpAuthenticationSettings.getSessionStore(StoreManager)` → `getSessionDataSet(Injector)`
|
|
89
|
+
- `HttpUserContext.getUserStore()` → `getUserDataSet()`
|
|
90
|
+
- `HttpUserContext.getSessionStore()` → `getSessionDataSet()`
|
|
91
|
+
- `authenticateUserWithStore()` → `authenticateUserWithDataSet()` — renamed helper with updated signature
|
|
92
|
+
- `useHttpAuthentication()` now requires DataSets for `User` and `DefaultSession` to be registered via `getRepository(injector).createDataSet()` before calling
|
|
93
|
+
|
|
94
|
+
### 🔄 Migration
|
|
95
|
+
|
|
96
|
+
**Setup:**
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Before
|
|
100
|
+
useHttpAuthentication(injector, {
|
|
101
|
+
getUserStore: (sm) => sm.getStoreFor(User, 'username'),
|
|
102
|
+
getSessionStore: (sm) => sm.getStoreFor(DefaultSession, 'sessionId'),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// After — register DataSets first, defaults resolve them automatically
|
|
106
|
+
getRepository(injector).createDataSet(User, 'username')
|
|
107
|
+
getRepository(injector).createDataSet(DefaultSession, 'sessionId')
|
|
108
|
+
useHttpAuthentication(injector)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Custom store accessors:**
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Before
|
|
115
|
+
settings.getUserStore(storeManager)
|
|
116
|
+
settings.getSessionStore(storeManager)
|
|
117
|
+
|
|
118
|
+
// After
|
|
119
|
+
settings.getUserDataSet(injector)
|
|
120
|
+
settings.getSessionDataSet(injector)
|
|
121
|
+
```
|
|
122
|
+
|
|
3
123
|
## [11.0.7] - 2026-02-22
|
|
4
124
|
|
|
5
125
|
### ⬆️ Dependencies
|
package/README.md
CHANGED
|
@@ -210,14 +210,15 @@ You can use the built-in authentication that comes with this package. It contain
|
|
|
210
210
|
```ts
|
|
211
211
|
import { useHttpAuthentication, useRestService } from '@furystack/rest-service'
|
|
212
212
|
import { Injector } from '@furystack/inject'
|
|
213
|
+
import { getDataSetFor } from '@furystack/repository'
|
|
213
214
|
|
|
214
215
|
const myInjector = new Injector()
|
|
215
216
|
useHttpAuthentication(myInjector, {
|
|
216
217
|
cookieName: 'sessionId', // The session ID will be stored in this cookie
|
|
217
218
|
enableBasicAuth: true, // Enables / disables standard Basic Authentication
|
|
218
219
|
model: ApplicationUserModel, // The custom User model. Should implement `User`
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
getUserDataSet: (injector) => getDataSetFor(injector, ApplicationUserModel, 'username'),
|
|
221
|
+
getSessionDataSet: (injector) => getDataSetFor(injector, MySessionModel, 'sessionId'),
|
|
221
222
|
})
|
|
222
223
|
await useRestService<MyApi>({ injector: myInjector, ...apiOptions })
|
|
223
224
|
```
|
package/esm/actions/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA"}
|
package/esm/actions/index.js
CHANGED
package/esm/actions/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,4BAA4B,CAAA"}
|
package/esm/actions/login.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { User } from '@furystack/core';
|
|
2
2
|
import type { RequestAction } from '../request-action-implementation.js';
|
|
3
3
|
/**
|
|
4
|
-
* Action that logs in the current user
|
|
5
|
-
* Should be called with a JSON
|
|
6
|
-
* Returns the current user instance
|
|
4
|
+
* Action that logs in the current user.
|
|
5
|
+
* Should be called with a JSON POST body with `username` and `password` fields.
|
|
6
|
+
* Returns the current user instance.
|
|
7
|
+
*
|
|
8
|
+
* @deprecated Use `createPasswordLoginAction(createCookieLoginStrategy(injector))` instead.
|
|
9
|
+
* This static action resolves services from the request-scoped injector on
|
|
10
|
+
* every call; the factory approach captures them once at setup time.
|
|
7
11
|
*/
|
|
8
12
|
export declare const LoginAction: RequestAction<{
|
|
9
13
|
result: User;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/actions/login.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/actions/login.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAK3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AAGxE;;;;;;;;GAQG;AACH,eAAO,MAAM,WAAW,EAAE,aAAa,CAAC;IACtC,MAAM,EAAE,IAAI,CAAA;IACZ,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC7C,CAWA,CAAA"}
|
package/esm/actions/login.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { HttpUserContext } from '../http-user-context.js';
|
|
2
1
|
import { RequestError } from '@furystack/rest';
|
|
2
|
+
import { sleepAsync } from '@furystack/utils';
|
|
3
|
+
import { HttpUserContext } from '../http-user-context.js';
|
|
3
4
|
import { JsonResult } from '../request-action-implementation.js';
|
|
4
5
|
/**
|
|
5
|
-
* Action that logs in the current user
|
|
6
|
-
* Should be called with a JSON
|
|
7
|
-
* Returns the current user instance
|
|
6
|
+
* Action that logs in the current user.
|
|
7
|
+
* Should be called with a JSON POST body with `username` and `password` fields.
|
|
8
|
+
* Returns the current user instance.
|
|
9
|
+
*
|
|
10
|
+
* @deprecated Use `createPasswordLoginAction(createCookieLoginStrategy(injector))` instead.
|
|
11
|
+
* This static action resolves services from the request-scoped injector on
|
|
12
|
+
* every call; the factory approach captures them once at setup time.
|
|
8
13
|
*/
|
|
9
14
|
export const LoginAction = async ({ injector, getBody, response }) => {
|
|
10
15
|
const userContext = injector.getInstance(HttpUserContext);
|
|
@@ -14,7 +19,8 @@ export const LoginAction = async ({ injector, getBody, response }) => {
|
|
|
14
19
|
await userContext.cookieLogin(user, response);
|
|
15
20
|
return JsonResult(user, 200);
|
|
16
21
|
}
|
|
17
|
-
catch
|
|
22
|
+
catch {
|
|
23
|
+
await sleepAsync(Math.random() * 1000);
|
|
18
24
|
throw new RequestError('Login Failed', 400);
|
|
19
25
|
}
|
|
20
26
|
};
|
package/esm/actions/login.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/actions/login.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/actions/login.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAEzD,OAAO,EAAE,UAAU,EAAE,MAAM,qCAAqC,CAAA;AAEhE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,WAAW,GAGnB,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE;IAC7C,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC,eAAe,CAAC,CAAA;IACzD,MAAM,IAAI,GAAG,MAAM,OAAO,EAAE,CAAA;IAC5B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC7E,MAAM,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAC7C,OAAO,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;QACtC,MAAM,IAAI,YAAY,CAAC,cAAc,EAAE,GAAG,CAAC,CAAA;IAC7C,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LoginResponseStrategy } from '../login-response-strategy.js';
|
|
2
|
+
import type { RequestAction } from '../request-action-implementation.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a login {@link RequestAction} that authenticates a user by
|
|
5
|
+
* username + password, then delegates session/token creation to the
|
|
6
|
+
* provided {@link LoginResponseStrategy}.
|
|
7
|
+
*
|
|
8
|
+
* The return type is inferred from the strategy:
|
|
9
|
+
* - Cookie strategy -> `ActionResult<User>`
|
|
10
|
+
* - JWT strategy -> `ActionResult<{ accessToken: string; refreshToken: string }>`
|
|
11
|
+
*
|
|
12
|
+
* A random delay (0-1 s) is added on failure to mitigate timing attacks.
|
|
13
|
+
*
|
|
14
|
+
* @param strategy The login response strategy that produces the session/token result
|
|
15
|
+
* @returns A `RequestAction` that can be wired into a REST API route
|
|
16
|
+
*/
|
|
17
|
+
export declare const createPasswordLoginAction: <TResult>(strategy: LoginResponseStrategy<TResult>) => RequestAction<{
|
|
18
|
+
result: TResult;
|
|
19
|
+
body: {
|
|
20
|
+
username: string;
|
|
21
|
+
password: string;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
//# sourceMappingURL=password-login-action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-login-action.d.ts","sourceRoot":"","sources":["../../src/actions/password-login-action.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAA;AAExE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAO,EAC/C,UAAU,qBAAqB,CAAC,OAAO,CAAC,KACvC,aAAa,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAWjF,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RequestError } from '@furystack/rest';
|
|
2
|
+
import { sleepAsync } from '@furystack/utils';
|
|
3
|
+
import { HttpUserContext } from '../http-user-context.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates a login {@link RequestAction} that authenticates a user by
|
|
6
|
+
* username + password, then delegates session/token creation to the
|
|
7
|
+
* provided {@link LoginResponseStrategy}.
|
|
8
|
+
*
|
|
9
|
+
* The return type is inferred from the strategy:
|
|
10
|
+
* - Cookie strategy -> `ActionResult<User>`
|
|
11
|
+
* - JWT strategy -> `ActionResult<{ accessToken: string; refreshToken: string }>`
|
|
12
|
+
*
|
|
13
|
+
* A random delay (0-1 s) is added on failure to mitigate timing attacks.
|
|
14
|
+
*
|
|
15
|
+
* @param strategy The login response strategy that produces the session/token result
|
|
16
|
+
* @returns A `RequestAction` that can be wired into a REST API route
|
|
17
|
+
*/
|
|
18
|
+
export const createPasswordLoginAction = (strategy) => {
|
|
19
|
+
return async ({ injector, getBody }) => {
|
|
20
|
+
const body = await getBody();
|
|
21
|
+
try {
|
|
22
|
+
const user = await injector.getInstance(HttpUserContext).authenticateUser(body.username, body.password);
|
|
23
|
+
return strategy.createLoginResponse(user, injector);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
await sleepAsync(Math.random() * 1000);
|
|
27
|
+
throw new RequestError('Login Failed', 400);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=password-login-action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-login-action.js","sourceRoot":"","sources":["../../src/actions/password-login-action.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAIzD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,QAAwC,EAC0C,EAAE;IACpF,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QACrC,MAAM,IAAI,GAAG,MAAM,OAAO,EAAE,CAAA;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,eAAe,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;YACvG,OAAO,QAAQ,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;YACtC,MAAM,IAAI,YAAY,CAAC,cAAc,EAAE,GAAG,CAAC,CAAA;QAC7C,CAAC;IACH,CAAC,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-login-action.spec.d.ts","sourceRoot":"","sources":["../../src/actions/password-login-action.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { InMemoryStore, StoreManager, User, addStore } from '@furystack/core';
|
|
2
|
+
import { Injector } from '@furystack/inject';
|
|
3
|
+
import { getRepository } from '@furystack/repository';
|
|
4
|
+
import { PasswordAuthenticator, PasswordCredential, PasswordResetToken, usePasswordPolicy } from '@furystack/security';
|
|
5
|
+
import { usingAsync } from '@furystack/utils';
|
|
6
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
7
|
+
import { useHttpAuthentication } from '../helpers.js';
|
|
8
|
+
import { DefaultSession } from '../models/default-session.js';
|
|
9
|
+
import { JsonResult } from '../request-action-implementation.js';
|
|
10
|
+
import { createPasswordLoginAction } from './password-login-action.js';
|
|
11
|
+
const setupInjector = async (i, username, password) => {
|
|
12
|
+
addStore(i, new InMemoryStore({ model: User, primaryKey: 'username' }))
|
|
13
|
+
.addStore(new InMemoryStore({ model: DefaultSession, primaryKey: 'sessionId' }))
|
|
14
|
+
.addStore(new InMemoryStore({ model: PasswordCredential, primaryKey: 'userName' }))
|
|
15
|
+
.addStore(new InMemoryStore({ model: PasswordResetToken, primaryKey: 'token' }));
|
|
16
|
+
const repo = getRepository(i);
|
|
17
|
+
repo.createDataSet(User, 'username');
|
|
18
|
+
repo.createDataSet(DefaultSession, 'sessionId');
|
|
19
|
+
repo.createDataSet(PasswordCredential, 'userName');
|
|
20
|
+
repo.createDataSet(PasswordResetToken, 'token');
|
|
21
|
+
usePasswordPolicy(i);
|
|
22
|
+
useHttpAuthentication(i);
|
|
23
|
+
const sm = i.getInstance(StoreManager);
|
|
24
|
+
const pw = i.getInstance(PasswordAuthenticator);
|
|
25
|
+
const cred = await pw.hasher.createCredential(username, password);
|
|
26
|
+
await sm.getStoreFor(PasswordCredential, 'userName').add(cred);
|
|
27
|
+
await sm.getStoreFor(User, 'username').add({ username, roles: ['admin'] });
|
|
28
|
+
};
|
|
29
|
+
const mockStrategy = {
|
|
30
|
+
createLoginResponse: vi.fn(async (user) => JsonResult(user, 200)),
|
|
31
|
+
};
|
|
32
|
+
describe('createPasswordLoginAction', () => {
|
|
33
|
+
const request = {};
|
|
34
|
+
const response = {};
|
|
35
|
+
it('Should delegate to the strategy on successful authentication', async () => {
|
|
36
|
+
await usingAsync(new Injector(), async (i) => {
|
|
37
|
+
await setupInjector(i, 'testuser', 'testpass');
|
|
38
|
+
const action = createPasswordLoginAction(mockStrategy);
|
|
39
|
+
const result = await action({
|
|
40
|
+
injector: i,
|
|
41
|
+
request,
|
|
42
|
+
response,
|
|
43
|
+
getBody: () => Promise.resolve({ username: 'testuser', password: 'testpass' }),
|
|
44
|
+
});
|
|
45
|
+
expect(mockStrategy.createLoginResponse).toHaveBeenCalled();
|
|
46
|
+
expect(result.chunk.username).toBe('testuser');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('Should pass the correct user to the strategy', async () => {
|
|
50
|
+
await usingAsync(new Injector(), async (i) => {
|
|
51
|
+
await setupInjector(i, 'testuser', 'testpass');
|
|
52
|
+
const strategyFn = vi.fn(async (user) => JsonResult(user, 200));
|
|
53
|
+
const strategy = { createLoginResponse: strategyFn };
|
|
54
|
+
const action = createPasswordLoginAction(strategy);
|
|
55
|
+
await action({
|
|
56
|
+
injector: i,
|
|
57
|
+
request,
|
|
58
|
+
response,
|
|
59
|
+
getBody: () => Promise.resolve({ username: 'testuser', password: 'testpass' }),
|
|
60
|
+
});
|
|
61
|
+
expect(strategyFn).toHaveBeenCalledWith(expect.objectContaining({ username: 'testuser', roles: ['admin'] }), i);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
it('Should throw RequestError on invalid credentials', async () => {
|
|
65
|
+
await usingAsync(new Injector(), async (i) => {
|
|
66
|
+
await setupInjector(i, 'testuser', 'testpass');
|
|
67
|
+
const action = createPasswordLoginAction(mockStrategy);
|
|
68
|
+
await expect(action({
|
|
69
|
+
injector: i,
|
|
70
|
+
request,
|
|
71
|
+
response,
|
|
72
|
+
getBody: () => Promise.resolve({ username: 'testuser', password: 'wrongpass' }),
|
|
73
|
+
})).rejects.toThrow('Login Failed');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
it('Should throw RequestError for nonexistent user', async () => {
|
|
77
|
+
await usingAsync(new Injector(), async (i) => {
|
|
78
|
+
await setupInjector(i, 'testuser', 'testpass');
|
|
79
|
+
const action = createPasswordLoginAction(mockStrategy);
|
|
80
|
+
await expect(action({
|
|
81
|
+
injector: i,
|
|
82
|
+
request,
|
|
83
|
+
response,
|
|
84
|
+
getBody: () => Promise.resolve({ username: 'nobody', password: 'nopass' }),
|
|
85
|
+
})).rejects.toThrow('Login Failed');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
it('Should work with a custom result type from strategy', async () => {
|
|
89
|
+
await usingAsync(new Injector(), async (i) => {
|
|
90
|
+
await setupInjector(i, 'testuser', 'testpass');
|
|
91
|
+
const tokenStrategy = {
|
|
92
|
+
createLoginResponse: async () => JsonResult({ accessToken: 'tok123' }, 200),
|
|
93
|
+
};
|
|
94
|
+
const action = createPasswordLoginAction(tokenStrategy);
|
|
95
|
+
const result = await action({
|
|
96
|
+
injector: i,
|
|
97
|
+
request,
|
|
98
|
+
response,
|
|
99
|
+
getBody: () => Promise.resolve({ username: 'testuser', password: 'testpass' }),
|
|
100
|
+
});
|
|
101
|
+
expect(result.chunk.accessToken).toBe('tok123');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=password-login-action.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-login-action.spec.js","sourceRoot":"","sources":["../../src/actions/password-login-action.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../src/authenticate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAgB,aAAa,EAAwB,MAAM,oCAAoC,CAAA;AAG3G;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,SAEtB,CAAC,SAAS;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,EAAE,QAAQ,aAAa,CAAC,CAAC,CAAC,KAAG,aAAa,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"authenticate.d.ts","sourceRoot":"","sources":["../src/authenticate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAgB,aAAa,EAAwB,MAAM,oCAAoC,CAAA;AAG3G;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,SAEtB,CAAC,SAAS;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,EAAE,QAAQ,aAAa,CAAC,CAAC,CAAC,KAAG,aAAa,CAAC,CAAC,CAqBzE,CAAA"}
|
package/esm/authenticate.js
CHANGED
|
@@ -27,7 +27,10 @@ export const Authenticate = () => (action) => {
|
|
|
27
27
|
const authenticated = await isAuthenticated(injector);
|
|
28
28
|
if (!authenticated) {
|
|
29
29
|
await sleepAsync(Math.random() * 1000);
|
|
30
|
-
|
|
30
|
+
const hasBasicAuth = injector
|
|
31
|
+
.getInstance(HttpUserContext)
|
|
32
|
+
.authentication.authenticationProviders.some((p) => p.name === 'basic-auth');
|
|
33
|
+
return JsonResult({ error: 'unauthorized' }, 401, hasBasicAuth ? { 'WWW-Authenticate': 'Basic' } : {});
|
|
31
34
|
}
|
|
32
35
|
return (await action(args));
|
|
33
36
|
};
|
package/esm/authenticate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.js","sourceRoot":"","sources":["../src/authenticate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAE/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,YAAY,GACvB,GAAG,EAAE,CACL,CAAgC,MAAwB,EAAoB,EAAE;IAC5E,MAAM,OAAO,GAAG,KAAK,EAAE,IAA6B,EAA4B,EAAE;QAChF,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAA;QACzB,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAA;QACrD,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;YACtC,
|
|
1
|
+
{"version":3,"file":"authenticate.js","sourceRoot":"","sources":["../src/authenticate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAE/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,YAAY,GACvB,GAAG,EAAE,CACL,CAAgC,MAAwB,EAAoB,EAAE;IAC5E,MAAM,OAAO,GAAG,KAAK,EAAE,IAA6B,EAA4B,EAAE;QAChF,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAA;QACzB,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAA;QACrD,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAA;YACtC,MAAM,YAAY,GAAG,QAAQ;iBAC1B,WAAW,CAAC,eAAe,CAAC;iBAC5B,cAAc,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAA;YAC9E,OAAO,UAAU,CACf,EAAE,KAAK,EAAE,cAAc,EAAE,EACzB,GAAG,EACH,YAAY,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CACtB,CAAA;QACjC,CAAC;QACD,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAoB,CAAA;IAChD,CAAC,CAAA;IAED,OAAO,CAAC,eAAe,GAAG,IAAI,CAAA;IAE9B,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
|
package/esm/authenticate.spec.js
CHANGED
|
@@ -8,12 +8,12 @@ import { EmptyResult } from './request-action-implementation.js';
|
|
|
8
8
|
describe('Authenticate', () => {
|
|
9
9
|
const response = {};
|
|
10
10
|
const request = { url: 'http://google.com' };
|
|
11
|
-
it('Should return
|
|
11
|
+
it('Should return 401 without WWW-Authenticate header when no basic-auth provider is registered', async () => {
|
|
12
12
|
await usingAsync(new Injector(), async (i) => {
|
|
13
13
|
const isAuthenticatedAction = vi.fn(async () => false);
|
|
14
14
|
i.setExplicitInstance({ isAuthenticated: isAuthenticatedAction, getCurrentUser: async () => Promise.reject(new Error(':(')) }, IdentityContext);
|
|
15
15
|
i.setExplicitInstance({
|
|
16
|
-
authentication: {
|
|
16
|
+
authentication: { authenticationProviders: [] },
|
|
17
17
|
}, HttpUserContext);
|
|
18
18
|
const exampleAuthenticatedAction = vi.fn(async () => EmptyResult());
|
|
19
19
|
const authorized = Authenticate()(exampleAuthenticatedAction);
|
|
@@ -24,13 +24,15 @@ describe('Authenticate', () => {
|
|
|
24
24
|
expect(exampleAuthenticatedAction).not.toBeCalled();
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
|
-
it('Should return
|
|
27
|
+
it('Should return 401 with WWW-Authenticate: Basic header when basic-auth provider is registered', async () => {
|
|
28
28
|
await usingAsync(new Injector(), async (i) => {
|
|
29
29
|
const isAuthenticatedAction = vi.fn(async () => false);
|
|
30
30
|
i.setExplicitInstance({
|
|
31
31
|
isAuthenticated: isAuthenticatedAction,
|
|
32
32
|
getCurrentUser: async () => Promise.reject(new Error(':(')),
|
|
33
|
-
authentication: {
|
|
33
|
+
authentication: {
|
|
34
|
+
authenticationProviders: [{ name: 'basic-auth', authenticate: async () => null }],
|
|
35
|
+
},
|
|
34
36
|
}, HttpUserContext);
|
|
35
37
|
const exampleAuthenticatedAction = vi.fn(async () => EmptyResult());
|
|
36
38
|
const authorized = Authenticate()(exampleAuthenticatedAction);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authenticate.spec.js","sourceRoot":"","sources":["../src/authenticate.spec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAA;AAEhE,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,MAAM,QAAQ,GAAG,EAA2B,CAAA;IAC5C,MAAM,OAAO,GAAG,EAAE,GAAG,EAAE,mBAAmB,EAAqB,CAAA;IAE/D,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"authenticate.spec.js","sourceRoot":"","sources":["../src/authenticate.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { User } from '@furystack/core';
|
|
2
|
+
import type { IncomingMessage } from 'http';
|
|
3
|
+
/**
|
|
4
|
+
* Interface for pluggable authentication providers.
|
|
5
|
+
*
|
|
6
|
+
* Each provider attempts to authenticate an HTTP request using
|
|
7
|
+
* a specific mechanism (e.g. Basic Auth, Cookie, JWT Bearer).
|
|
8
|
+
*
|
|
9
|
+
* **Error handling contract:**
|
|
10
|
+
* - Returns `User` if authentication succeeded.
|
|
11
|
+
* - Returns `null` if this provider does not apply to the request
|
|
12
|
+
* (e.g. no relevant header present). The next provider in the chain will be tried.
|
|
13
|
+
* - Throws if the provider applies but authentication fails due to
|
|
14
|
+
* bad credentials or an infrastructure error (DB down, etc.).
|
|
15
|
+
* Throwing skips remaining providers and results in a 401/500.
|
|
16
|
+
*/
|
|
17
|
+
export type AuthenticationProvider = {
|
|
18
|
+
/**
|
|
19
|
+
* Identifies the provider for debugging and logging purposes only.
|
|
20
|
+
* Not used for deduplication or lookup.
|
|
21
|
+
*/
|
|
22
|
+
readonly name: string;
|
|
23
|
+
authenticate: (request: Pick<IncomingMessage, 'headers'>) => Promise<User | null>;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=authentication-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authentication-provider.d.ts","sourceRoot":"","sources":["../../src/authentication-providers/authentication-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAA;AAE3C;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,KAAK,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;CAClF,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authentication-provider.js","sourceRoot":"","sources":["../../src/authentication-providers/authentication-provider.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { User } from '@furystack/core';
|
|
2
|
+
import type { AuthenticationProvider } from './authentication-provider.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates an authentication provider for HTTP Basic Authentication.
|
|
5
|
+
* @param authenticateUser Callback that verifies credentials and returns a User or throws
|
|
6
|
+
*/
|
|
7
|
+
export declare const createBasicAuthProvider: (authenticateUser: (username: string, password: string) => Promise<User>) => AuthenticationProvider;
|
|
8
|
+
//# sourceMappingURL=basic-auth-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basic-auth-provider.d.ts","sourceRoot":"","sources":["../../src/authentication-providers/basic-auth-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAE1E;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAClC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,KACtE,sBAWD,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates an authentication provider for HTTP Basic Authentication.
|
|
3
|
+
* @param authenticateUser Callback that verifies credentials and returns a User or throws
|
|
4
|
+
*/
|
|
5
|
+
export const createBasicAuthProvider = (authenticateUser) => ({
|
|
6
|
+
name: 'basic-auth',
|
|
7
|
+
authenticate: async (request) => {
|
|
8
|
+
const authHeader = request.headers.authorization;
|
|
9
|
+
if (!authHeader?.startsWith('Basic '))
|
|
10
|
+
return null;
|
|
11
|
+
const decoded = Buffer.from(authHeader.split(' ')[1], 'base64').toString();
|
|
12
|
+
const colonIndex = decoded.indexOf(':');
|
|
13
|
+
const userName = decoded.slice(0, colonIndex);
|
|
14
|
+
const password = decoded.slice(colonIndex + 1);
|
|
15
|
+
return await authenticateUser(userName, password);
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=basic-auth-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basic-auth-provider.js","sourceRoot":"","sources":["../../src/authentication-providers/basic-auth-provider.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,gBAAuE,EAC/C,EAAE,CAAC,CAAC;IAC5B,IAAI,EAAE,YAAY;IAClB,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC9B,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAA;QAChD,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAA;QAClD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC1E,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAA;QAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAA;QAC9C,OAAO,MAAM,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;IACnD,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { User } from '@furystack/core';
|
|
2
|
+
import type { DefaultSession } from '../models/default-session.js';
|
|
3
|
+
import type { AuthenticationProvider } from './authentication-provider.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates an authentication provider for cookie-based session authentication.
|
|
6
|
+
* @param cookieName The name of the session cookie
|
|
7
|
+
* @param getSessionById Callback to find a session by ID
|
|
8
|
+
* @param getUserByName Callback to find a user by username
|
|
9
|
+
*/
|
|
10
|
+
export declare const createCookieAuthProvider: (cookieName: string, getSessionById: (sessionId: string) => Promise<DefaultSession | null>, getUserByName: (username: string) => Promise<User>) => AuthenticationProvider;
|
|
11
|
+
//# sourceMappingURL=cookie-auth-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-auth-provider.d.ts","sourceRoot":"","sources":["../../src/authentication-providers/cookie-auth-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAE3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAG1E;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,GACnC,YAAY,MAAM,EAClB,gBAAgB,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,EACrE,eAAe,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,KACjD,sBASD,CAAA"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { UnauthenticatedError } from '@furystack/security';
|
|
2
|
+
import { extractSessionIdFromCookies } from './helpers.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates an authentication provider for cookie-based session authentication.
|
|
5
|
+
* @param cookieName The name of the session cookie
|
|
6
|
+
* @param getSessionById Callback to find a session by ID
|
|
7
|
+
* @param getUserByName Callback to find a user by username
|
|
8
|
+
*/
|
|
9
|
+
export const createCookieAuthProvider = (cookieName, getSessionById, getUserByName) => ({
|
|
10
|
+
name: 'cookie-auth',
|
|
11
|
+
authenticate: async (request) => {
|
|
12
|
+
const sessionId = extractSessionIdFromCookies(request, cookieName);
|
|
13
|
+
if (!sessionId)
|
|
14
|
+
return null;
|
|
15
|
+
const session = await getSessionById(sessionId);
|
|
16
|
+
if (!session)
|
|
17
|
+
throw new UnauthenticatedError();
|
|
18
|
+
return await getUserByName(session.username);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=cookie-auth-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookie-auth-provider.js","sourceRoot":"","sources":["../../src/authentication-providers/cookie-auth-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAG1D,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAA;AAE1D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,UAAkB,EAClB,cAAqE,EACrE,aAAkD,EAC1B,EAAE,CAAC,CAAC;IAC5B,IAAI,EAAE,aAAa;IACnB,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC9B,MAAM,SAAS,GAAG,2BAA2B,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;QAClE,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAA;QAC3B,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,CAAA;QAC/C,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,oBAAoB,EAAE,CAAA;QAC9C,OAAO,MAAM,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9C,CAAC;CACF,CAAC,CAAA"}
|