@alxia/janus 0.1.1 → 0.2.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/README.md +26 -14
- package/dist/errors.d.ts +17 -7
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -31
- package/dist/index.js.map +5 -5
- package/dist/permission.d.ts +6 -5
- package/dist/permission.d.ts.map +1 -1
- package/dist/session.d.ts +21 -21
- package/dist/session.d.ts.map +1 -1
- package/docs/README.md +2 -2
- package/docs/guide/errors.md +52 -23
- package/docs/guide/permissions.md +15 -11
- package/docs/guide/sessions.md +31 -19
- package/docs/guide/sign-in-and-out.md +13 -13
- package/docs/roadmap.md +6 -2
- package/docs/troubleshooting.md +91 -56
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Identities, sessions and permissions for [alxia](https://www.npmjs.com/package/@alxia/core),
|
|
4
4
|
on [`@nxgt/janus`](https://www.npmjs.com/package/@nxgt/janus): your process,
|
|
5
5
|
your database. The user typed in the context, the session cookie kept and
|
|
6
|
-
renewed, janus's refusals answered
|
|
6
|
+
renewed, janus's refusals answered with their status and a safe body. No
|
|
7
7
|
dependency.
|
|
8
8
|
|
|
9
9
|
```sh
|
|
@@ -17,7 +17,7 @@ the same cookie, the same rules, the same bodies.
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
-
import { alxia } from '@alxia/core';
|
|
20
|
+
import { alxia, validate } from '@alxia/core';
|
|
21
21
|
import { janusErrors, session } from '@alxia/janus';
|
|
22
22
|
import { createMemoryStores, janus, scryptHasher } from '@nxgt/janus';
|
|
23
23
|
import { z } from 'zod';
|
|
@@ -32,22 +32,27 @@ const accounts = janus({
|
|
|
32
32
|
const SignIn = z.object({ email: z.string(), password: z.string() });
|
|
33
33
|
|
|
34
34
|
const app = alxia()
|
|
35
|
-
.use(janusErrors())
|
|
36
|
-
.
|
|
37
|
-
.post('/signin', { body: SignIn }, async ({ body, auth, reply }) => {
|
|
35
|
+
.use(janusErrors(), session(accounts)) // errors first, then the session: anonymous may sign in
|
|
36
|
+
.post('/signin', validate({ body: SignIn }), async ({ body, auth, reply }) => {
|
|
38
37
|
const signedIn = await accounts.signIn(body);
|
|
39
38
|
return reply.ok({ id: auth.send(signedIn).id }); // the token in the cookie
|
|
40
39
|
})
|
|
41
40
|
.post('/signout', async ({ auth, reply }) => reply.ok(await auth.signOut()))
|
|
42
|
-
.use(session(accounts, { required: true })) //
|
|
41
|
+
.use(session(accounts, { required: true })) // the routes after it, and any unmatched request
|
|
43
42
|
.get('/me', ({ user, reply }) => reply.ok(user)); // user typed by the schema
|
|
44
43
|
```
|
|
45
44
|
|
|
45
|
+
`janusErrors()` and `session()` are middlewares. `janusErrors()` answers
|
|
46
|
+
what is thrown behind it, so give it to `use` **before** `session()`.
|
|
47
|
+
|
|
46
48
|
## `session(accounts, options?)`
|
|
47
49
|
|
|
48
50
|
The routes after it read `user`, `session` and `auth`. With
|
|
49
51
|
`required: true`, an anonymous request is a 401
|
|
50
|
-
`{ error: 'unauthenticated' }` and `user` is never `null
|
|
52
|
+
`{ error: 'unauthenticated' }` and `user` is never `null`. On the app, a
|
|
53
|
+
required session also runs on a request no route matches: an anonymous
|
|
54
|
+
request to a missing path gets the 401, not the 404. Put it in a `group` to
|
|
55
|
+
guard only some routes; without, it is
|
|
51
56
|
`null` for an anonymous request. A `boolean` known only at run time types
|
|
52
57
|
both: the 401, and a `user` that may be `null`. `type` narrows to one user
|
|
53
58
|
type of a multi-type `janus()`; `device` names the device cookie.
|
|
@@ -77,11 +82,17 @@ behind `required: true`, an anonymous request — anyone signing in — is a
|
|
|
77
82
|
|
|
78
83
|
## `janusErrors(options?)`
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
A try/catch middleware: every `JanusError` thrown behind it — by
|
|
86
|
+
`session()`, `permission()` or a route — is answered with janus's status
|
|
81
87
|
and a body holding its `code` and only what a client can act on — the
|
|
82
88
|
issues, a minimum length, attempts left, seconds to wait (with
|
|
83
89
|
`Retry-After`). Never a login, a reason or a cause. `report(error, ctx)` is
|
|
84
|
-
called for the 5xx.
|
|
90
|
+
called for the 5xx. Anything else goes on, thrown, to the middlewares before
|
|
91
|
+
it. Declared after `session()`, it would not see the session's
|
|
92
|
+
`STORE_FAILED`: it goes first, and after the observers (`logger()`,
|
|
93
|
+
`secureHeaders()`), so that they see its reply. An observer settles
|
|
94
|
+
`next()` without swallowing the error, so `janusErrors()` before one, or
|
|
95
|
+
before `createI18n()`, still answers it.
|
|
85
96
|
|
|
86
97
|
| status | codes |
|
|
87
98
|
| --- | --- |
|
|
@@ -145,7 +156,7 @@ types exactly then.
|
|
|
145
156
|
### Reading the app's context
|
|
146
157
|
|
|
147
158
|
Annotate `load`, `subject` or `ctx`'s parameter to read what an earlier
|
|
148
|
-
|
|
159
|
+
middleware added. The guard then requires it: an app that does not give it
|
|
149
160
|
before the guard cannot use it.
|
|
150
161
|
|
|
151
162
|
```ts
|
|
@@ -159,7 +170,7 @@ const byTenant = permission(
|
|
|
159
170
|
tenant.records.get(pathParams['id'] ?? '') ?? null,
|
|
160
171
|
);
|
|
161
172
|
|
|
162
|
-
alxia().
|
|
173
|
+
alxia().plugin(tenancy).use(session(accounts)).use(byTenant); // tenancy derives tenant
|
|
163
174
|
alxia().use(session(accounts)).use(byTenant); // a compile error: this app gives no `tenant`
|
|
164
175
|
```
|
|
165
176
|
|
|
@@ -170,13 +181,14 @@ on every app: annotate what it reads, or leave it unannotated.
|
|
|
170
181
|
|
|
171
182
|
| export | |
|
|
172
183
|
| --- | --- |
|
|
173
|
-
| `session(accounts, options?)` | the
|
|
184
|
+
| `session(accounts, options?)` | the middleware: `user`, `session`, `auth` |
|
|
174
185
|
| `SessionOptions` | its options: `type`, `required`, `device` |
|
|
175
186
|
| `RequestAuth`, `SessionOpened` | the type of `ctx.auth`: `send`, `signOut`, `device`; and what `send` takes: `token`, `session` and `user` from `@nxgt/janus`'s `SignedIn`, and `deviceToken?` |
|
|
176
187
|
| `sendSession`, `signOut`, `deviceOf`, `sendDevice` | the cookies, outside a route |
|
|
177
188
|
| `DEVICE_COOKIE` | the device cookie's name, `janus-device`: the one `@nxgt/janus-hono` uses, so a device one remembers the other does too |
|
|
178
189
|
| `SendSessionOptions`, `DeviceCookieOptions` | their options: `device`; `name`, `domain`, `path`, `sameSite`, `secure`, `maxAge` |
|
|
179
|
-
| `janusErrors(options?)` | the
|
|
190
|
+
| `janusErrors(options?)` | the middleware: janus's refusals answered; `use` it before `session()` |
|
|
191
|
+
| `JanusErrors`, `SessionMiddleware` | the types of what `janusErrors()` and `session()` make |
|
|
180
192
|
| `JanusErrorsOptions` | its options: `report`, called with every error answered 5xx |
|
|
181
193
|
| `permission(…)`, `byParam(…)` | the guard |
|
|
182
194
|
| `PermissionOptions`, `OptionsArgs` | its options: `subject`, `ctx`; and the rest of its arguments, the options required exactly when the permission has a condition; both take what `subject` and `ctx` read beyond `BaseContext` as their last two, defaulted, parameters |
|
|
@@ -186,6 +198,6 @@ on every app: annotate what it reads, or leave it unannotated.
|
|
|
186
198
|
|
|
187
199
|
## Documentation
|
|
188
200
|
|
|
189
|
-
- [Guide](https://github.com/softistx/alxia/tree/develop/packages/janus/docs): a page per area — sessions, signing in and out with `ctx.auth` and the device cookie, janus's errors and their statuses, and the permission guard, reading what an earlier
|
|
201
|
+
- [Guide](https://github.com/softistx/alxia/tree/develop/packages/janus/docs): a page per area — sessions, signing in and out with `ctx.auth` and the device cookie, janus's errors and their statuses, and the permission guard, reading what an earlier middleware added included.
|
|
190
202
|
- [Troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/janus/docs/troubleshooting.md): an error message, or a request anonymous, refused or a 404 when it should not be, and what to do about it.
|
|
191
203
|
- [Roadmap](https://github.com/softistx/alxia/blob/develop/packages/janus/docs/roadmap.md): what is coming, and what is not planned.
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BaseContext } from '@alxia/core';
|
|
1
|
+
import { type BaseContext, type Middleware, type MiddlewareMark, type Next, type Reply } from '@alxia/core';
|
|
2
2
|
import { JanusError, type JanusErrorCode, type JanusErrorStatus, statusOf } from '@nxgt/janus';
|
|
3
3
|
/**
|
|
4
4
|
* The body a refusal is answered with: its `code`, and only what the client
|
|
@@ -22,16 +22,26 @@ export interface JanusErrorsOptions {
|
|
|
22
22
|
*/
|
|
23
23
|
readonly report?: (error: JanusError, ctx: BaseContext) => unknown;
|
|
24
24
|
}
|
|
25
|
+
/** What `janusErrors()` makes: a middleware that answers what janus throws behind it. */
|
|
26
|
+
export type JanusErrors = Middleware<object, Promise<Next | Reply<JanusErrorStatus, JanusErrorBody>>> & MiddlewareMark;
|
|
25
27
|
/**
|
|
26
|
-
* Janus's errors answered, as a
|
|
27
|
-
*
|
|
28
|
-
* answered with janus's status
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* Janus's errors answered, as a middleware: every `JanusError` thrown
|
|
29
|
+
* behind it — by `session()`, `permission()`, a route — a sign-in
|
|
30
|
+
* refused, a login taken, a store down — is answered with janus's status
|
|
31
|
+
* and `bodyOf(error)`, typed on the routes declared after it. A throttled
|
|
32
|
+
* sign-in carries `Retry-After`. Anything else goes on, thrown, to the
|
|
33
|
+
* middlewares before it, the app's `onError` or its 500.
|
|
34
|
+
*
|
|
35
|
+
* Give it to `use` before `session()`, so that a store down while the
|
|
36
|
+
* session is read is answered too:
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* app.use(janusErrors(), session(accounts)).post('/sign-in', ...);
|
|
40
|
+
* ```
|
|
31
41
|
*
|
|
32
42
|
* **`STORE_FAILED` is a 503**, never a 401 or a 404: an outage is not an
|
|
33
43
|
* answer.
|
|
34
44
|
*/
|
|
35
|
-
export declare function janusErrors(options?: JanusErrorsOptions):
|
|
45
|
+
export declare function janusErrors(options?: JanusErrorsOptions): JanusErrors;
|
|
36
46
|
export { statusOf };
|
|
37
47
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,WAAW,EAEhB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,KAAK,EACV,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,QAAQ,EACR,MAAM,aAAa,CAAC;AAErB;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;IACvC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CAmBxD;AAED,MAAM,WAAW,kBAAkB;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC;CACnE;AAED,yFAAyF;AACzF,MAAM,MAAM,WAAW,GAAG,UAAU,CACnC,MAAM,EACN,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC,CACvD,GACA,cAAc,CAAC;AAEhB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,WAAW,CASzE;AA0BD,OAAO,EAAE,QAAQ,EAAE,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { DEVICE_COOKIE, type DeviceCookieOptions, deviceOf, sendDevice, } from './device';
|
|
2
|
-
export { bodyOf, type JanusErrorBody, type JanusErrorsOptions, janusErrors, statusOf, } from './errors';
|
|
2
|
+
export { bodyOf, type JanusErrorBody, type JanusErrors, type JanusErrorsOptions, janusErrors, statusOf, } from './errors';
|
|
3
3
|
export { byParam, type PermissionRefusedBody, permission, } from './permission';
|
|
4
4
|
export type { Awaitable, ObjectData, OptionsArgs, PermissionOptions, } from './permission-options';
|
|
5
5
|
export { type SendSessionOptions, sendSession, signOut } from './send';
|
|
6
|
-
export { type SessionOptions, session, type UnauthenticatedBody, } from './session';
|
|
6
|
+
export { type SessionMiddleware, type SessionOptions, session, type UnauthenticatedBody, } from './session';
|
|
7
7
|
export type { Auth, RequestAuth, SessionOpened, UserOfAuth } from './types';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EACb,KAAK,mBAAmB,EACxB,QAAQ,EACR,UAAU,GACV,MAAM,UAAU,CAAC;AAClB,OAAO,EACN,MAAM,EACN,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,WAAW,EACX,QAAQ,GACR,MAAM,UAAU,CAAC;AAClB,OAAO,EACN,OAAO,EACP,KAAK,qBAAqB,EAC1B,UAAU,GACV,MAAM,cAAc,CAAC;AACtB,YAAY,EACX,SAAS,EACT,UAAU,EACV,WAAW,EACX,iBAAiB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACvE,OAAO,EACN,KAAK,cAAc,EACnB,OAAO,EACP,KAAK,mBAAmB,GACxB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,aAAa,EACb,KAAK,mBAAmB,EACxB,QAAQ,EACR,UAAU,GACV,MAAM,UAAU,CAAC;AAClB,OAAO,EACN,MAAM,EACN,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,WAAW,EACX,QAAQ,GACR,MAAM,UAAU,CAAC;AAClB,OAAO,EACN,OAAO,EACP,KAAK,qBAAqB,EAC1B,UAAU,GACV,MAAM,cAAc,CAAC;AACtB,YAAY,EACX,SAAS,EACT,UAAU,EACV,WAAW,EACX,iBAAiB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,KAAK,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACvE,OAAO,EACN,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,OAAO,EACP,KAAK,mBAAmB,GACxB,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,9 @@ function sendDevice(ctx, token, options = {}) {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
// src/errors.ts
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
defineMiddleware
|
|
22
|
+
} from "@alxia/core";
|
|
21
23
|
import {
|
|
22
24
|
JanusError,
|
|
23
25
|
statusOf
|
|
@@ -37,19 +39,28 @@ function bodyOf(error) {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
function janusErrors(options = {}) {
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
return;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
return defineMiddleware(async (ctx, next) => {
|
|
43
|
+
try {
|
|
44
|
+
return await next();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (!(error instanceof JanusError))
|
|
47
|
+
throw error;
|
|
48
|
+
return answer(error, ctx, options);
|
|
46
49
|
}
|
|
47
|
-
const body = bodyOf(error);
|
|
48
|
-
return ctx.reply(status, body, error.retryAfter === undefined ? {} : { headers: { "retry-after": String(error.retryAfter) } });
|
|
49
50
|
});
|
|
50
51
|
}
|
|
52
|
+
function answer(error, ctx, options) {
|
|
53
|
+
const status = statusOf(error.code);
|
|
54
|
+
if (status >= 500 && options.report !== undefined) {
|
|
55
|
+
Promise.resolve().then(() => options.report?.(error, ctx)).catch((failure) => process.emitWarning(`janusErrors(): report failed: ${String(failure)}`));
|
|
56
|
+
}
|
|
57
|
+
const body = bodyOf(error);
|
|
58
|
+
return ctx.reply(status, body, error.retryAfter === undefined ? {} : { headers: { "retry-after": String(error.retryAfter) } });
|
|
59
|
+
}
|
|
51
60
|
// src/permission.ts
|
|
52
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
defineMiddleware as defineMiddleware2
|
|
63
|
+
} from "@alxia/core";
|
|
53
64
|
function permission(access, permission, type, load, ...options) {
|
|
54
65
|
const loadOf = load;
|
|
55
66
|
const { subject, ctx: ctxOf } = options[0] ?? {};
|
|
@@ -58,7 +69,7 @@ function permission(access, permission, type, load, ...options) {
|
|
|
58
69
|
const body = { error };
|
|
59
70
|
return body;
|
|
60
71
|
};
|
|
61
|
-
return
|
|
72
|
+
return defineMiddleware2()(async (ctx, next) => {
|
|
62
73
|
const who = subject === undefined ? userOf(ctx) : await subject(ctx);
|
|
63
74
|
if (who === null)
|
|
64
75
|
return ctx.reply(401, refuse("unauthenticated"));
|
|
@@ -68,8 +79,8 @@ function permission(access, permission, type, load, ...options) {
|
|
|
68
79
|
const allowed = await can(who, permission, view(object, type), ctxOf === undefined ? undefined : { ctx: await ctxOf(ctx, object) });
|
|
69
80
|
if (!allowed)
|
|
70
81
|
return ctx.reply(403, refuse("forbidden"));
|
|
71
|
-
return { object };
|
|
72
|
-
})
|
|
82
|
+
return next({ object });
|
|
83
|
+
});
|
|
73
84
|
}
|
|
74
85
|
function byParam(name, find) {
|
|
75
86
|
return (ctx) => {
|
|
@@ -105,7 +116,8 @@ async function signOut(ctx, auth) {
|
|
|
105
116
|
}
|
|
106
117
|
// src/session.ts
|
|
107
118
|
import {
|
|
108
|
-
|
|
119
|
+
defineMiddleware as defineMiddleware3,
|
|
120
|
+
settle,
|
|
109
121
|
withHeaders
|
|
110
122
|
} from "@alxia/core";
|
|
111
123
|
|
|
@@ -138,12 +150,10 @@ function authenticateOnce(auth, request, type) {
|
|
|
138
150
|
|
|
139
151
|
// src/session.ts
|
|
140
152
|
function session(auth, options = {}) {
|
|
141
|
-
const current = new WeakMap;
|
|
142
153
|
const unauthenticated = { error: "unauthenticated" };
|
|
143
|
-
return
|
|
154
|
+
return defineMiddleware3(async (ctx, next) => {
|
|
144
155
|
const { request, reply } = ctx;
|
|
145
156
|
const found = await authenticateOnce(auth, request, options.type);
|
|
146
|
-
current.set(request, found);
|
|
147
157
|
if (found === null && options.required === true) {
|
|
148
158
|
return reply(401, unauthenticated);
|
|
149
159
|
}
|
|
@@ -152,24 +162,22 @@ function session(auth, options = {}) {
|
|
|
152
162
|
send: (signedIn) => sendSession(ctx, auth, signedIn, options),
|
|
153
163
|
signOut: () => signOut(ctx, auth)
|
|
154
164
|
};
|
|
155
|
-
|
|
165
|
+
const response = await settle(ctx, next({
|
|
156
166
|
user: found?.user ?? null,
|
|
157
167
|
session: found?.session ?? null,
|
|
158
168
|
auth: bound
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const response = await next();
|
|
162
|
-
const found = current.get(request);
|
|
163
|
-
if (found?.renewed !== true)
|
|
164
|
-
return response;
|
|
165
|
-
const name = `${auth.cookie.name}=`;
|
|
166
|
-
const presented = new Bun.CookieMap(request.headers.get("cookie") ?? "").get(auth.cookie.name);
|
|
167
|
-
if (presented !== found.token || response.headers.getSetCookie().some((value) => value.startsWith(name))) {
|
|
168
|
-
return response;
|
|
169
|
-
}
|
|
170
|
-
return withHeaders(response, (headers) => headers.append("set-cookie", auth.cookie.serialize(found.token, found.session)));
|
|
169
|
+
}));
|
|
170
|
+
return found?.renewed === true ? renewed(auth, request, found, response) : response;
|
|
171
171
|
});
|
|
172
172
|
}
|
|
173
|
+
function renewed(auth, request, found, response) {
|
|
174
|
+
const name = `${auth.cookie.name}=`;
|
|
175
|
+
const presented = new Bun.CookieMap(request.headers.get("cookie") ?? "").get(auth.cookie.name);
|
|
176
|
+
if (presented !== found.token || response.headers.getSetCookie().some((value) => value.startsWith(name))) {
|
|
177
|
+
return response;
|
|
178
|
+
}
|
|
179
|
+
return withHeaders(response, (headers) => headers.append("set-cookie", auth.cookie.serialize(found.token, found.session)));
|
|
180
|
+
}
|
|
173
181
|
export {
|
|
174
182
|
DEVICE_COOKIE,
|
|
175
183
|
bodyOf,
|
|
@@ -184,5 +192,5 @@ export {
|
|
|
184
192
|
statusOf
|
|
185
193
|
};
|
|
186
194
|
|
|
187
|
-
//# debugId=
|
|
195
|
+
//# debugId=B189B0B99B3C49B664756E2164756E21
|
|
188
196
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
"sources": ["../src/device.ts", "../src/errors.ts", "../src/permission.ts", "../src/send.ts", "../src/session.ts", "../src/lookup.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { ResponseSettings } from '@alxia/core';\n\n/** `@nxgt/janus-hono`'s name for it, so a device remembered by one is by the other. */\nexport const DEVICE_COOKIE = 'janus-device';\n\nconst MAX_AGE = 400 * 24 * 60 * 60;\n\nexport interface DeviceCookieOptions {\n\treadonly name?: string;\n\treadonly domain?: string;\n\treadonly path?: string;\n\treadonly sameSite?: 'lax' | 'strict' | 'none';\n\treadonly secure?: boolean;\n\t/** Seconds. Four hundred days, the most a browser keeps, by default. */\n\treadonly maxAge?: number;\n}\n\n/** The device token the request carries, for `signIn(…, { device })`: `null` when none. */\nexport function deviceOf(\n\tctx: { readonly request: Request },\n\toptions: Pick<DeviceCookieOptions, 'name'> = {},\n): string | null {\n\tconst cookies = new Bun.CookieMap(ctx.request.headers.get('cookie') ?? '');\n\tconst value = cookies.get(options.name ?? DEVICE_COOKIE);\n\treturn value === null || value === '' ? null : value;\n}\n\n/** Sets the device cookie, or sets it again so it lasts another `maxAge`. */\nexport function sendDevice(\n\tctx: { readonly set: ResponseSettings },\n\ttoken: string,\n\toptions: DeviceCookieOptions = {},\n): void {\n\tctx.set.cookies.set(options.name ?? DEVICE_COOKIE, token, {\n\t\thttpOnly: true,\n\t\tpath: options.path ?? '/',\n\t\tsameSite: options.sameSite ?? 'lax',\n\t\tsecure: options.secure ?? true,\n\t\tmaxAge: options.maxAge ?? MAX_AGE,\n\t\t...(options.domain === undefined ? {} : { domain: options.domain }),\n\t});\n}\n",
|
|
6
|
-
"import {
|
|
7
|
-
"import {
|
|
6
|
+
"import {\n\ttype BaseContext,\n\tdefineMiddleware,\n\ttype Middleware,\n\ttype MiddlewareMark,\n\ttype Next,\n\ttype Reply,\n} from '@alxia/core';\nimport {\n\tJanusError,\n\ttype JanusErrorCode,\n\ttype JanusErrorStatus,\n\tstatusOf,\n} from '@nxgt/janus';\n\n/**\n * The body a refusal is answered with: its `code`, and only what the client\n * can act on. Never `reason`, `login`, a hash prefix or a cause: those are\n * for your logs. `@nxgt/janus-hono`'s `bodyOf`, kept twice on purpose.\n */\nexport interface JanusErrorBody {\n\treadonly code: JanusErrorCode;\n\treadonly issues?: JanusError['issues'];\n\treadonly minLength?: number;\n\treadonly attemptsLeft?: number;\n\treadonly retryAfter?: number;\n}\n\nexport function bodyOf(error: JanusError): JanusErrorBody {\n\tswitch (error.code) {\n\t\tcase 'USER_INVALID':\n\t\t\treturn { code: error.code, issues: error.issues ?? [] };\n\t\tcase 'PASSWORD_TOO_SHORT':\n\t\t\treturn error.minLength === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, minLength: error.minLength };\n\t\tcase 'CODE_INVALID':\n\t\t\treturn error.attemptsLeft === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, attemptsLeft: error.attemptsLeft };\n\t\tcase 'CREDENTIALS_INVALID':\n\t\t\treturn error.retryAfter === undefined\n\t\t\t\t? { code: error.code }\n\t\t\t\t: { code: error.code, retryAfter: error.retryAfter };\n\t\tdefault:\n\t\t\treturn { code: error.code };\n\t}\n}\n\nexport interface JanusErrorsOptions {\n\t/**\n\t * Called with every `JanusError` answered 5xx — `STORE_FAILED` and the\n\t * like, the server's to fix — as it is answered: started, not awaited,\n\t * so a slow report never holds the response. It cannot change the\n\t * answer: one that throws or rejects is a warning.\n\t */\n\treadonly report?: (error: JanusError, ctx: BaseContext) => unknown;\n}\n\n/** What `janusErrors()` makes: a middleware that answers what janus throws behind it. */\nexport type JanusErrors = Middleware<\n\tobject,\n\tPromise<Next | Reply<JanusErrorStatus, JanusErrorBody>>\n> &\n\tMiddlewareMark;\n\n/**\n * Janus's errors answered, as a middleware: every `JanusError` thrown\n * behind it — by `session()`, `permission()`, a route — a sign-in\n * refused, a login taken, a store down — is answered with janus's status\n * and `bodyOf(error)`, typed on the routes declared after it. A throttled\n * sign-in carries `Retry-After`. Anything else goes on, thrown, to the\n * middlewares before it, the app's `onError` or its 500.\n *\n * Give it to `use` before `session()`, so that a store down while the\n * session is read is answered too:\n *\n * ```ts\n * app.use(janusErrors(), session(accounts)).post('/sign-in', ...);\n * ```\n *\n * **`STORE_FAILED` is a 503**, never a 401 or a 404: an outage is not an\n * answer.\n */\nexport function janusErrors(options: JanusErrorsOptions = {}): JanusErrors {\n\treturn defineMiddleware(async (ctx, next) => {\n\t\ttry {\n\t\t\treturn await next();\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof JanusError)) throw error;\n\t\t\treturn answer(error, ctx, options);\n\t\t}\n\t});\n}\n\n/** A `JanusError`, answered: its status, its body, reported when a 5xx. */\nfunction answer(\n\terror: JanusError,\n\tctx: BaseContext,\n\toptions: JanusErrorsOptions,\n): Reply<JanusErrorStatus, JanusErrorBody> {\n\tconst status: JanusErrorStatus = statusOf(error.code);\n\tif (status >= 500 && options.report !== undefined) {\n\t\tPromise.resolve()\n\t\t\t.then(() => options.report?.(error, ctx))\n\t\t\t.catch((failure: unknown) =>\n\t\t\t\tprocess.emitWarning(`janusErrors(): report failed: ${String(failure)}`),\n\t\t\t);\n\t}\n\tconst body = bodyOf(error);\n\treturn ctx.reply(\n\t\tstatus,\n\t\tbody,\n\t\terror.retryAfter === undefined\n\t\t\t? {}\n\t\t\t: { headers: { 'retry-after': String(error.retryAfter) } },\n\t);\n}\n\nexport { statusOf };\n",
|
|
7
|
+
"import {\n\ttype BaseContext,\n\tdefineMiddleware,\n\ttype RequiresOf,\n} from '@alxia/core';\nimport type {\n\tCheckableOf,\n\tModelConfig,\n\tObjectTypeOf,\n\tPermissions,\n\tSubjectRef,\n} from '@nxgt/janus/permissions';\nimport type { Awaitable, ObjectData, OptionsArgs } from './permission-options';\n\n/** The body of each refusal. */\nexport interface PermissionRefusedBody {\n\treadonly error: 'unauthenticated' | 'not_found' | 'forbidden';\n}\n\ntype LooseCan = (\n\tsubject: SubjectRef<ModelConfig> | null,\n\tpermission: string,\n\tobject: { readonly type: string; readonly id: string },\n\toptions?: { readonly ctx: unknown },\n) => Promise<boolean>;\n\n/**\n * A guard, as a middleware: the routes declared after it run only if the\n * subject holds `permission` on an object of `type`. It loads the object\n * once, checks it with `access.can`, and hands it to them as `object`.\n *\n * | the request | answered |\n * | --- | --- |\n * | anonymous | 401 — before the object is loaded |\n * | `load` answers `null` | 404 |\n * | a denial | 403 |\n * | allowed | the route runs, `object` set |\n *\n * Each refusal is typed on those routes. **A failure throws**: a store that\n * cannot answer is `STORE_FAILED`, never a 403 — `janusErrors()` answers\n * it 503. Scope it with `group`, so it guards only its routes — given to\n * the app's `use`, it would refuse a request no route matches too:\n *\n * ```ts\n * app.use(janusErrors(), session(accounts)).group('/records/:id', (records) =>\n * records.use(permission(access, 'view', 'record', byParam('id', findRecord)))\n * .get('/', ({ object, reply }) => reply(200, object)));\n * ```\n */\nexport function permission<\n\tC extends ModelConfig,\n\tconst T extends ObjectTypeOf<C>,\n\tconst P extends CheckableOf<C, T>,\n\tO extends ObjectData<C, T>,\n\tLoadCtx extends object = BaseContext,\n\tSubjectCtx extends object = BaseContext,\n\tCheckCtx extends object = BaseContext,\n>(\n\taccess: Pick<Permissions<C>, 'can'>,\n\tpermission: P,\n\ttype: T,\n\tload: (ctx: BaseContext & LoadCtx) => Awaitable<O | null>,\n\t...options: OptionsArgs<C, T, P, O, SubjectCtx, CheckCtx>\n) {\n\t// `app.use` has checked that the app gives what `load`, `subject` and `ctx` read.\n\tconst loadOf = load as (ctx: BaseContext) => Awaitable<O | null>;\n\tconst { subject, ctx: ctxOf } = (options[0] ?? {}) as {\n\t\treadonly subject?: (\n\t\t\tctx: BaseContext,\n\t\t) => Awaitable<SubjectRef<ModelConfig> | null>;\n\t\treadonly ctx?: (ctx: BaseContext, object: unknown) => Awaitable<unknown>;\n\t};\n\tconst can = access.can as LooseCan;\n\tconst refuse = (error: PermissionRefusedBody['error']) => {\n\t\tconst body: PermissionRefusedBody = { error };\n\t\treturn body;\n\t};\n\treturn defineMiddleware<\n\t\tRequiresOf<\n\t\t\tLoadCtx & SubjectCtx & CheckCtx,\n\t\t\tAnnotatedAny<LoadCtx, SubjectCtx>\n\t\t>\n\t>()(async (ctx, next) => {\n\t\tconst who = subject === undefined ? userOf(ctx) : await subject(ctx);\n\t\tif (who === null) return ctx.reply(401, refuse('unauthenticated'));\n\t\tconst object = await loadOf(ctx);\n\t\tif (object === null) return ctx.reply(404, refuse('not_found'));\n\t\tconst allowed = await can(\n\t\t\twho,\n\t\t\tpermission,\n\t\t\tview(object, type),\n\t\t\tctxOf === undefined ? undefined : { ctx: await ctxOf(ctx, object) },\n\t\t);\n\t\tif (!allowed) return ctx.reply(403, refuse('forbidden'));\n\t\treturn next({ object });\n\t});\n}\n\n/**\n * The callback to name when `RequiresOf` refuses one annotated `any`: `load`\n * or `subject` when it is that one, `ctx` otherwise. An `any` in any of the\n * three makes their intersection `any`, so `RequiresOf` cannot tell which;\n * a `Record<string, any>` passes the same test.\n */\ntype AnnotatedAny<LoadCtx, SubjectCtx> = [LoadCtx] extends [\n\t{ readonly '~any': 1 },\n]\n\t? 'load'\n\t: [SubjectCtx] extends [{ readonly '~any': 1 }]\n\t\t? 'subject'\n\t\t: 'ctx';\n\n/**\n * A `load` reading one path parameter, as it arrived: `find(id)`, or `null`\n * — a 404 — when the route has no such parameter.\n */\nexport function byParam<O>(\n\tname: string,\n\tfind: (id: string) => Awaitable<O | null>,\n): (ctx: BaseContext) => Awaitable<O | null> {\n\treturn (ctx) => {\n\t\tconst id = ctx.pathParams[name];\n\t\treturn id === undefined ? null : find(id);\n\t};\n}\n\n/** The object as `can()` reads it: `type` added, every other field read from the object itself. */\nfunction view(\n\tobject: object,\n\ttype: string,\n): { readonly type: string; readonly id: string } {\n\treturn new Proxy(object, {\n\t\tget: (target, key) => (key === 'type' ? type : Reflect.get(target, key)),\n\t\thas: (target, key) => key === 'type' || Reflect.has(target, key),\n\t}) as { readonly type: string; readonly id: string };\n}\n\n/** The `user` `session()` derived — or a wiring error when nothing did. */\nfunction userOf(ctx: BaseContext): SubjectRef<ModelConfig> | null {\n\tconst user = (ctx as { readonly user?: unknown }).user;\n\tif (user === undefined) {\n\t\tthrow new TypeError(\n\t\t\t'permission(): no user in the context — use session(auth) before it, or pass { subject }',\n\t\t);\n\t}\n\treturn user as SubjectRef<ModelConfig> | null;\n}\n",
|
|
8
8
|
"import type { ResponseSettings } from '@alxia/core';\nimport { type DeviceCookieOptions, sendDevice } from './device';\nimport type { Auth, SessionOpened } from './types';\n\n/** What `sendSession` takes besides: the device cookie's options. */\nexport interface SendSessionOptions {\n\treadonly device?: DeviceCookieOptions;\n}\n\n/**\n * Sends the session cookie — after `signUp`, `signIn`, anything that\n * answered a token and its session — and answers the user. The token is in\n * the cookie, never in the body. With a `deviceToken`, the device cookie\n * too.\n *\n * ```ts\n * const signedIn = await auth.signIn(body, { device: deviceOf(ctx) });\n * return reply(200, { id: sendSession(ctx, auth, signedIn).id });\n * ```\n */\nexport function sendSession<U>(\n\tctx: { readonly set: ResponseSettings },\n\tauth: Pick<Auth<{ readonly type: string }>, 'cookie'>,\n\tsignedIn: SessionOpened<U>,\n\toptions: SendSessionOptions = {},\n): U {\n\tctx.set.headers.append(\n\t\t'set-cookie',\n\t\tauth.cookie.serialize(signedIn.token, signedIn.session),\n\t);\n\tif (typeof signedIn.deviceToken === 'string') {\n\t\tsendDevice(ctx, signedIn.deviceToken, options.device);\n\t}\n\treturn signedIn.user;\n}\n\n/**\n * Revokes the session the request presents and clears the cookie — cleared\n * whatever the answer, so a browser holding a stale cookie drops it too.\n */\nexport async function signOut(\n\tctx: { readonly request: Request; readonly set: ResponseSettings },\n\tauth: Pick<Auth<{ readonly type: string }>, 'signOut' | 'cookie'>,\n): Promise<boolean> {\n\tconst revoked = await auth.signOut(ctx.request);\n\tctx.set.headers.append('set-cookie', auth.cookie.clear());\n\treturn revoked;\n}\n",
|
|
9
|
-
"import {\n\ttype
|
|
9
|
+
"import {\n\tdefineMiddleware,\n\ttype Empty,\n\ttype Middleware,\n\ttype MiddlewareMark,\n\ttype Next,\n\ttype Reply,\n\tsettle,\n\twithHeaders,\n} from '@alxia/core';\nimport type { Session } from '@nxgt/janus';\nimport { type DeviceCookieOptions, deviceOf } from './device';\nimport { authenticateOnce, type Found } from './lookup';\nimport { sendSession, signOut } from './send';\nimport type { Auth, RequestAuth, UserOfAuth } from './types';\n\nexport interface SessionOptions<T extends string> {\n\t/** Only a user of this type is authenticated here; any other is anonymous. */\n\treadonly type?: T;\n\t/**\n\t * `true`: an anonymous request is answered 401 and the route never runs,\n\t * so `user` is never `null` in it — and the 401 is in its type. A\n\t * `boolean` known only at runtime keeps the 401 in the type, and `user`\n\t * may be `null`.\n\t */\n\treadonly required?: boolean;\n\t/** The device cookie `auth.device` reads and `auth.send` sets. */\n\treadonly device?: DeviceCookieOptions;\n}\n\n/** The body of the 401 a required session answers. */\nexport interface UnauthenticatedBody {\n\treadonly error: 'unauthenticated';\n}\n\ntype UserOf<A, T> = Extract<UserOfAuth<A>, { readonly type: T }>;\n\n/** What a session gives the routes after it. */\ntype Given<User, S> = {\n\treadonly user: User;\n\treadonly session: S;\n\treadonly auth: RequestAuth;\n};\n\n/** What `session()` makes: a middleware that gives `Added`, or answers `Refused`. */\nexport type SessionMiddleware<\n\tAdded extends object,\n\tRefused = never,\n> = Middleware<Empty, Promise<Refused | Next<Added>>> & MiddlewareMark;\n\n/**\n * Who a request belongs to, as a middleware: `auth.authenticate(request)`,\n * and the routes declared after it read `user` and `session` — typed by\n * the user schema, narrowed by `type`. Given to `app.use`, it runs on a\n * request no route matches too: a required session answers it 401 before\n * its 404.\n *\n * **An outage is not anonymous**: a store that cannot answer throws\n * `STORE_FAILED`, which `janusErrors()` — given to `use` before it —\n * answers 503, never 401.\n *\n * A session renewed in passing is sent again as a cookie, after the route —\n * only to a request that presented it as one (a `Bearer` client is never\n * handed a cookie), and never over a session cookie the route set itself.\n *\n * ```ts\n * app.use(janusErrors(), session(accounts, { required: true }))\n * .get('/me', ({ user, reply }) => reply.ok(user));\n * ```\n */\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions: SessionOptions<T> & { readonly required: true },\n): SessionMiddleware<\n\tGiven<UserOf<A, T>, Session>,\n\tReply<401, UnauthenticatedBody>\n>;\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions?: SessionOptions<T> & { readonly required?: false },\n): SessionMiddleware<Given<UserOf<A, T> | null, Session | null>>;\nexport function session<\n\tA extends Auth<{ readonly type: string }>,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(\n\tauth: A,\n\toptions?: SessionOptions<T>,\n): SessionMiddleware<\n\tGiven<UserOf<A, T> | null, Session | null>,\n\tReply<401, UnauthenticatedBody>\n>;\nexport function session(\n\tauth: Auth<{ readonly type: string }>,\n\toptions: SessionOptions<string> = {},\n): unknown {\n\tconst unauthenticated: UnauthenticatedBody = { error: 'unauthenticated' };\n\treturn defineMiddleware(async (ctx, next) => {\n\t\tconst { request, reply } = ctx;\n\t\tconst found = await authenticateOnce(auth, request, options.type);\n\t\tif (found === null && options.required === true) {\n\t\t\treturn reply(401, unauthenticated);\n\t\t}\n\t\tconst bound: RequestAuth = {\n\t\t\tdevice: deviceOf(ctx, options.device),\n\t\t\tsend: (signedIn) => sendSession(ctx, auth, signedIn, options),\n\t\t\tsignOut: () => signOut(ctx, auth),\n\t\t};\n\t\t// Settled: a refusal's 400, an error's answer, still carry the renewal.\n\t\tconst response = await settle(\n\t\t\tctx,\n\t\t\tnext({\n\t\t\t\tuser: found?.user ?? null,\n\t\t\t\tsession: found?.session ?? null,\n\t\t\t\tauth: bound,\n\t\t\t}),\n\t\t);\n\t\treturn found?.renewed === true\n\t\t\t? renewed(auth, request, found, response)\n\t\t\t: response;\n\t});\n}\n\n/**\n * A session renewed in passing, sent again as a cookie: only to a request\n * that presented it as one, and never over a cookie the route set itself —\n * a sign-in or a sign-out behind it must not be undone by the session it\n * replaced.\n */\nfunction renewed<R extends Response>(\n\tauth: Auth<{ readonly type: string }>,\n\trequest: Request,\n\tfound: NonNullable<Found>,\n\tresponse: R,\n): R {\n\tconst name = `${auth.cookie.name}=`;\n\tconst presented = new Bun.CookieMap(request.headers.get('cookie') ?? '').get(\n\t\tauth.cookie.name,\n\t);\n\tif (\n\t\tpresented !== found.token ||\n\t\tresponse.headers.getSetCookie().some((value) => value.startsWith(name))\n\t) {\n\t\treturn response;\n\t}\n\treturn withHeaders(response, (headers) =>\n\t\theaders.append(\n\t\t\t'set-cookie',\n\t\t\tauth.cookie.serialize(found.token, found.session),\n\t\t),\n\t) as R;\n}\n",
|
|
10
10
|
"import type { Authenticated } from '@nxgt/janus';\nimport type { Auth } from './types';\n\nexport type Found = Authenticated<{ readonly type: string }> | null;\n\n/**\n * Each request's lookups, by instance and by `type`: an open `session()`\n * and a required one behind it, of the same `type`, look the session up\n * once. They live as long as the `Request` object; a lookup that failed is\n * forgotten, so the next dispatch of that object asks again.\n */\nconst lookups = new WeakMap<\n\tobject,\n\tWeakMap<Request, Map<string, Promise<Found>>>\n>();\n\nexport function authenticateOnce(\n\tauth: Auth<{ readonly type: string }>,\n\trequest: Request,\n\ttype: string | undefined,\n): Promise<Found> {\n\tlet byRequest = lookups.get(auth);\n\tif (byRequest === undefined) {\n\t\tbyRequest = new WeakMap();\n\t\tlookups.set(auth, byRequest);\n\t}\n\tlet byType = byRequest.get(request);\n\tif (byType === undefined) {\n\t\tbyType = new Map();\n\t\tbyRequest.set(request, byType);\n\t}\n\tconst key = type ?? '';\n\tlet found = byType.get(key);\n\tif (found === undefined) {\n\t\tfound = auth.authenticate(\n\t\t\trequest,\n\t\t\ttype === undefined ? undefined : { type },\n\t\t);\n\t\tbyType.set(key, found);\n\t\tconst asked = found;\n\t\tasked.catch(() => {\n\t\t\tif (byType.get(key) === asked) byType.delete(key);\n\t\t});\n\t}\n\treturn found;\n}\n"
|
|
11
11
|
],
|
|
12
|
-
"mappings": ";AAGO,IAAM,gBAAgB;AAE7B,IAAM,UAAU,MAAM,KAAK,KAAK;AAazB,SAAS,QAAQ,CACvB,KACA,UAA6C,CAAC,GAC9B;AAAA,EAChB,MAAM,UAAU,IAAI,IAAI,UAAU,IAAI,QAAQ,QAAQ,IAAI,QAAQ,KAAK,EAAE;AAAA,EACzE,MAAM,QAAQ,QAAQ,IAAI,QAAQ,QAAQ,aAAa;AAAA,EACvD,OAAO,UAAU,QAAQ,UAAU,KAAK,OAAO;AAAA;AAIzC,SAAS,UAAU,CACzB,KACA,OACA,UAA+B,CAAC,GACzB;AAAA,EACP,IAAI,IAAI,QAAQ,IAAI,QAAQ,QAAQ,eAAe,OAAO;AAAA,IACzD,UAAU;AAAA,IACV,MAAM,QAAQ,QAAQ;AAAA,IACtB,UAAU,QAAQ,YAAY;AAAA,IAC9B,QAAQ,QAAQ,UAAU;AAAA,IAC1B,QAAQ,QAAQ,UAAU;AAAA,OACtB,QAAQ,WAAW,YAAY,CAAC,IAAI,EAAE,QAAQ,QAAQ,OAAO;AAAA,EAClE,CAAC;AAAA;;ACxCF;
|
|
13
|
-
"debugId": "
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "B189B0B99B3C49B664756E2164756E21",
|
|
14
14
|
"names": []
|
|
15
15
|
}
|
package/dist/permission.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface PermissionRefusedBody {
|
|
|
6
6
|
readonly error: 'unauthenticated' | 'not_found' | 'forbidden';
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* A guard, as a
|
|
9
|
+
* A guard, as a middleware: the routes declared after it run only if the
|
|
10
10
|
* subject holds `permission` on an object of `type`. It loads the object
|
|
11
11
|
* once, checks it with `access.can`, and hands it to them as `object`.
|
|
12
12
|
*
|
|
@@ -19,17 +19,18 @@ export interface PermissionRefusedBody {
|
|
|
19
19
|
*
|
|
20
20
|
* Each refusal is typed on those routes. **A failure throws**: a store that
|
|
21
21
|
* cannot answer is `STORE_FAILED`, never a 403 — `janusErrors()` answers
|
|
22
|
-
* it 503. Scope it with `group`, so it guards only its routes
|
|
22
|
+
* it 503. Scope it with `group`, so it guards only its routes — given to
|
|
23
|
+
* the app's `use`, it would refuse a request no route matches too:
|
|
23
24
|
*
|
|
24
25
|
* ```ts
|
|
25
|
-
* app.use(session(accounts)).group('/records/:id', (records) =>
|
|
26
|
+
* app.use(janusErrors(), session(accounts)).group('/records/:id', (records) =>
|
|
26
27
|
* records.use(permission(access, 'view', 'record', byParam('id', findRecord)))
|
|
27
28
|
* .get('/', ({ object, reply }) => reply(200, object)));
|
|
28
29
|
* ```
|
|
29
30
|
*/
|
|
30
|
-
export declare function permission<C extends ModelConfig, const T extends ObjectTypeOf<C>, const P extends CheckableOf<C, T>, O extends ObjectData<C, T>, LoadCtx extends object = BaseContext, SubjectCtx extends object = BaseContext, CheckCtx extends object = BaseContext>(access: Pick<Permissions<C>, 'can'>, permission: P, type: T, load: (ctx: BaseContext & LoadCtx) => Awaitable<O | null>, ...options: OptionsArgs<C, T, P, O, SubjectCtx, CheckCtx>): import("@alxia/core").
|
|
31
|
+
export declare function permission<C extends ModelConfig, const T extends ObjectTypeOf<C>, const P extends CheckableOf<C, T>, O extends ObjectData<C, T>, LoadCtx extends object = BaseContext, SubjectCtx extends object = BaseContext, CheckCtx extends object = BaseContext>(access: Pick<Permissions<C>, 'can'>, permission: P, type: T, load: (ctx: BaseContext & LoadCtx) => Awaitable<O | null>, ...options: OptionsArgs<C, T, P, O, SubjectCtx, CheckCtx>): import("@alxia/core").Middleware<RequiresOf<LoadCtx & SubjectCtx & CheckCtx, AnnotatedAny<LoadCtx, SubjectCtx>>, Promise<import("@alxia/core").Reply<401, PermissionRefusedBody> | import("@alxia/core").Reply<404, PermissionRefusedBody> | import("@alxia/core").Reply<403, PermissionRefusedBody> | import("@alxia/core").Next<{
|
|
31
32
|
object: O;
|
|
32
|
-
}, import("@alxia/core").Empty
|
|
33
|
+
}, import("@alxia/core").Empty>>> & import("@alxia/core").MiddlewareMark;
|
|
33
34
|
/**
|
|
34
35
|
* The callback to name when `RequiresOf` refuses one annotated `any`: `load`
|
|
35
36
|
* or `subject` when it is that one, `ctx` otherwise. An `any` in any of the
|
package/dist/permission.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../src/permission.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,WAAW,EAEhB,KAAK,UAAU,EACf,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACX,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EAEX,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE/E,gCAAgC;AAChC,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,WAAW,GAAG,WAAW,CAAC;CAC9D;AASD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CACzB,CAAC,SAAS,WAAW,EACrB,KAAK,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,EAC/B,KAAK,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACjC,CAAC,SAAS,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,OAAO,SAAS,MAAM,GAAG,WAAW,EACpC,UAAU,SAAS,MAAM,GAAG,WAAW,EACvC,QAAQ,SAAS,MAAM,GAAG,WAAW,EAErC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EACnC,UAAU,EAAE,CAAC,EACb,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,EACzD,GAAG,OAAO,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC;;yEAkCzD;AAED;;;;;GAKG;AACH,KAAK,YAAY,CAAC,OAAO,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,SAAS;IAC1D;QAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;KAAE;CACtB,GACE,MAAM,GACN,CAAC,UAAU,CAAC,SAAS,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,GAC5C,SAAS,GACT,KAAK,CAAC;AAEV;;;GAGG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACxB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,GACvC,CAAC,GAAG,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,CAK3C"}
|
package/dist/session.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Empty, type Middleware, type MiddlewareMark, type Next, type Reply } from '@alxia/core';
|
|
2
2
|
import type { Session } from '@nxgt/janus';
|
|
3
3
|
import { type DeviceCookieOptions } from './device';
|
|
4
4
|
import type { Auth, RequestAuth, UserOfAuth } from './types';
|
|
@@ -22,46 +22,46 @@ export interface UnauthenticatedBody {
|
|
|
22
22
|
type UserOf<A, T> = Extract<UserOfAuth<A>, {
|
|
23
23
|
readonly type: T;
|
|
24
24
|
}>;
|
|
25
|
+
/** What a session gives the routes after it. */
|
|
26
|
+
type Given<User, S> = {
|
|
27
|
+
readonly user: User;
|
|
28
|
+
readonly session: S;
|
|
29
|
+
readonly auth: RequestAuth;
|
|
30
|
+
};
|
|
31
|
+
/** What `session()` makes: a middleware that gives `Added`, or answers `Refused`. */
|
|
32
|
+
export type SessionMiddleware<Added extends object, Refused = never> = Middleware<Empty, Promise<Refused | Next<Added>>> & MiddlewareMark;
|
|
25
33
|
/**
|
|
26
|
-
* Who a request belongs to, as a
|
|
27
|
-
* the routes declared after it read `user` and `session` — typed by
|
|
28
|
-
* user schema, narrowed by `type`.
|
|
34
|
+
* Who a request belongs to, as a middleware: `auth.authenticate(request)`,
|
|
35
|
+
* and the routes declared after it read `user` and `session` — typed by
|
|
36
|
+
* the user schema, narrowed by `type`. Given to `app.use`, it runs on a
|
|
37
|
+
* request no route matches too: a required session answers it 401 before
|
|
38
|
+
* its 404.
|
|
29
39
|
*
|
|
30
40
|
* **An outage is not anonymous**: a store that cannot answer throws
|
|
31
|
-
* `STORE_FAILED`, which `janusErrors()`
|
|
41
|
+
* `STORE_FAILED`, which `janusErrors()` — given to `use` before it —
|
|
42
|
+
* answers 503, never 401.
|
|
32
43
|
*
|
|
33
44
|
* A session renewed in passing is sent again as a cookie, after the route —
|
|
34
45
|
* only to a request that presented it as one (a `Bearer` client is never
|
|
35
46
|
* handed a cookie), and never over a session cookie the route set itself.
|
|
36
47
|
*
|
|
37
48
|
* ```ts
|
|
38
|
-
* app.use(session(accounts, { required: true }))
|
|
49
|
+
* app.use(janusErrors(), session(accounts, { required: true }))
|
|
50
|
+
* .get('/me', ({ user, reply }) => reply.ok(user));
|
|
39
51
|
* ```
|
|
40
52
|
*/
|
|
41
53
|
export declare function session<A extends Auth<{
|
|
42
54
|
readonly type: string;
|
|
43
55
|
}>, const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(auth: A, options: SessionOptions<T> & {
|
|
44
56
|
readonly required: true;
|
|
45
|
-
}):
|
|
46
|
-
readonly user: UserOf<A, T>;
|
|
47
|
-
readonly session: Session;
|
|
48
|
-
readonly auth: RequestAuth;
|
|
49
|
-
}, Empty, '', Reply<401, UnauthenticatedBody>>;
|
|
57
|
+
}): SessionMiddleware<Given<UserOf<A, T>, Session>, Reply<401, UnauthenticatedBody>>;
|
|
50
58
|
export declare function session<A extends Auth<{
|
|
51
59
|
readonly type: string;
|
|
52
60
|
}>, const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(auth: A, options?: SessionOptions<T> & {
|
|
53
61
|
readonly required?: false;
|
|
54
|
-
}):
|
|
55
|
-
readonly user: UserOf<A, T> | null;
|
|
56
|
-
readonly session: Session | null;
|
|
57
|
-
readonly auth: RequestAuth;
|
|
58
|
-
}, Empty, '', never>;
|
|
62
|
+
}): SessionMiddleware<Given<UserOf<A, T> | null, Session | null>>;
|
|
59
63
|
export declare function session<A extends Auth<{
|
|
60
64
|
readonly type: string;
|
|
61
|
-
}>, const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(auth: A, options?: SessionOptions<T>):
|
|
62
|
-
readonly user: UserOf<A, T> | null;
|
|
63
|
-
readonly session: Session | null;
|
|
64
|
-
readonly auth: RequestAuth;
|
|
65
|
-
}, Empty, '', Reply<401, UnauthenticatedBody>>;
|
|
65
|
+
}>, const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(auth: A, options?: SessionOptions<T>): SessionMiddleware<Given<UserOf<A, T> | null, Session | null>, Reply<401, UnauthenticatedBody>>;
|
|
66
66
|
export {};
|
|
67
67
|
//# sourceMappingURL=session.d.ts.map
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/docs/README.md
CHANGED
|
@@ -10,7 +10,7 @@ a realistic example for each.
|
|
|
10
10
|
| --- | --- |
|
|
11
11
|
| [Sessions](guide/sessions.md) | reading who a request belongs to, requiring a session, narrowing to one user type, placing the sign-in routes before the required ones, knowing where the token is read from and when a renewed cookie is sent, or testing a signed-in route |
|
|
12
12
|
| [Signing in and out](guide/sign-in-and-out.md) | setting the session cookie with `ctx.auth.send` after a sign-up or a sign-in, handling a second factor, signing out, remembering devices with the device cookie, or doing any of it outside a route |
|
|
13
|
-
| [Errors](guide/errors.md) | answering janus's refusals with their status and a safe body, knowing which code is which status, reporting the 5xx, or reading a refusal on the
|
|
14
|
-
| [Permissions](guide/permissions.md) | guarding routes with a permission on an object, loading it by a path parameter, passing a condition's context, checking for another subject than the session's user, or reading what an earlier
|
|
13
|
+
| [Errors](guide/errors.md) | answering janus's refusals with their status and a safe body, knowing which code is which status, reporting the 5xx, or reading a refusal on the wire |
|
|
14
|
+
| [Permissions](guide/permissions.md) | guarding routes with a permission on an object, loading it by a path parameter, passing a condition's context, checking for another subject than the session's user, or reading what an earlier middleware added |
|
|
15
15
|
| [Troubleshooting](troubleshooting.md) | something went wrong and you have the message, or a request is anonymous, refused or a 404 when you did not expect it |
|
|
16
16
|
| [Roadmap](roadmap.md) | wondering what is coming, and what is not planned |
|