@knpkv/browser-pairing 0.2.0 → 0.3.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 +10 -1
- package/dist/owner-session.d.ts +141 -0
- package/dist/owner-session.d.ts.map +1 -0
- package/dist/owner-session.js +254 -0
- package/dist/owner-session.js.map +1 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
# @knpkv/browser-pairing
|
|
2
2
|
|
|
3
3
|
Typed, redacted browser-pairing credentials and transport primitives. Product
|
|
4
|
-
packages keep their own persistence, authorization, and route policy.
|
|
4
|
+
packages keep their own persistence, persisted authorization, and route policy.
|
|
5
|
+
|
|
6
|
+
`@knpkv/browser-pairing/owner-session` is the one shared policy: the Owner
|
|
7
|
+
Session of a single-operator loopback web app. It issues the session, CSRF
|
|
8
|
+
(`writes: "csrf"`) and bootstrap credentials, authorizes requests by session
|
|
9
|
+
cookie, Origin, Fetch Metadata and CSRF token, and serves `POST /auth/bootstrap`.
|
|
10
|
+
The service and its bootstrap response carry credentials; the session cookie is
|
|
11
|
+
the only value it sets on the browser. Applications keep their API middleware,
|
|
12
|
+
map `OwnerSessionUnauthorizedError` to 401 and `OwnerSessionForbiddenError` to
|
|
13
|
+
403, and pass the origin a browser request must carry. See ADR-0008.
|
|
5
14
|
|
|
6
15
|
`PairingCode`, `SessionToken`, and `CsrfToken` are distinct branded roles over
|
|
7
16
|
the same validated credential encoding. Consumers must issue and decode the
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who may talk to a single-operator loopback web app.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **One process, one operator.** The server binds a loopback address and mints a session token for
|
|
7
|
+
* itself at startup. There is no user table and no login: the person who can read the terminal is
|
|
8
|
+
* the person who gets in.
|
|
9
|
+
* - **The URL is the handshake.** A bootstrap code rides in a fragment the browser never sends
|
|
10
|
+
* upstream and is spent the first time it is exchanged for the session cookie. Codes come from
|
|
11
|
+
* {@link OwnerSessionService.mintBootstrapCode}, only once the server is listening; a newer code
|
|
12
|
+
* replaces an unspent one and reopens an exchange that guesses had closed.
|
|
13
|
+
* - **A read needs the cookie and must not be a browser cross-origin request; a write also needs the
|
|
14
|
+
* expected origin and the CSRF header.** Another page in the same browser can make the browser send
|
|
15
|
+
* a cookie, but Fetch Metadata keeps that page from starting reads without an Origin. An app with
|
|
16
|
+
* `writes: "none"` issues no CSRF token and refuses every unsafe method.
|
|
17
|
+
*
|
|
18
|
+
* Applications keep their HTTP API middleware and wire errors; they map
|
|
19
|
+
* {@link OwnerSessionUnauthorizedError} to 401 and {@link OwnerSessionForbiddenError} to 403.
|
|
20
|
+
* Multi-session, persisted authorization (Control Center) is out of scope.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import { Context, Crypto, Effect, Layer, Redacted, Schema } from "effect";
|
|
25
|
+
import { HttpRouter, HttpServerRequest } from "effect/http";
|
|
26
|
+
import type { BrowserPairingError } from "./crypto.js";
|
|
27
|
+
import type { CsrfToken, PairingCode } from "./schema.js";
|
|
28
|
+
import { CredentialCookieError } from "./schema.js";
|
|
29
|
+
/**
|
|
30
|
+
* The one origin other than the bound server that may be advertised: the Vite dev server that
|
|
31
|
+
* proxies the API and the bootstrap exchange.
|
|
32
|
+
*/
|
|
33
|
+
export declare const devPublicOrigin = "http://localhost:5173";
|
|
34
|
+
declare const OwnerSessionUnauthorizedError_base: Schema.Class<OwnerSessionUnauthorizedError, Schema.TaggedStruct<"OwnerSessionUnauthorizedError", {
|
|
35
|
+
readonly message: Schema.String;
|
|
36
|
+
}>, import("effect/Cause").YieldableError>;
|
|
37
|
+
/** The session cookie or bootstrap code is missing, wrong, spent, or expired. Maps to 401. */
|
|
38
|
+
export declare class OwnerSessionUnauthorizedError extends OwnerSessionUnauthorizedError_base {
|
|
39
|
+
}
|
|
40
|
+
declare const OwnerSessionForbiddenError_base: Schema.Class<OwnerSessionForbiddenError, Schema.TaggedStruct<"OwnerSessionForbiddenError", {
|
|
41
|
+
readonly message: Schema.String;
|
|
42
|
+
}>, import("effect/Cause").YieldableError>;
|
|
43
|
+
/** The credential is right but the request did not come from the owner's page. Maps to 403. */
|
|
44
|
+
export declare class OwnerSessionForbiddenError extends OwnerSessionForbiddenError_base {
|
|
45
|
+
}
|
|
46
|
+
declare const UnsafeLoopbackAddressError_base: Schema.Class<UnsafeLoopbackAddressError, Schema.TaggedStruct<"UnsafeLoopbackAddressError", {
|
|
47
|
+
readonly address: Schema.String;
|
|
48
|
+
readonly message: Schema.String;
|
|
49
|
+
}>, import("effect/Cause").YieldableError>;
|
|
50
|
+
/** A hostname or origin is not a plain HTTP loopback address. */
|
|
51
|
+
export declare class UnsafeLoopbackAddressError extends UnsafeLoopbackAddressError_base {
|
|
52
|
+
}
|
|
53
|
+
/** Hostnames a loopback web app may bind. */
|
|
54
|
+
export declare const LoopbackHostname: Schema.Literals<readonly ["127.0.0.1", "localhost", "::1"]>;
|
|
55
|
+
export type LoopbackHostname = typeof LoopbackHostname.Type;
|
|
56
|
+
/** Whether a URL hostname (which brackets IPv6) names the loopback interface. */
|
|
57
|
+
export declare const isLoopbackHostname: (hostname: string) => boolean;
|
|
58
|
+
/** Refuse to bind anything a peer on the network could reach. */
|
|
59
|
+
export declare const requireLoopbackHostname: (hostname: string) => Effect.Effect<"127.0.0.1" | "::1" | "localhost", UnsafeLoopbackAddressError, never>;
|
|
60
|
+
/** The HTTP origin for a bound loopback server, without a trailing slash. */
|
|
61
|
+
export declare const loopbackOrigin: (hostname: string, port: number) => string;
|
|
62
|
+
/** Validate an origin as plain HTTP on loopback with no path, query, or fragment. */
|
|
63
|
+
export declare const requireLoopbackOrigin: (origin: string) => Effect.Effect<string, UnsafeLoopbackAddressError, never>;
|
|
64
|
+
/** The origin to advertise: the bound server, or the dev server that proxies to it. */
|
|
65
|
+
export declare const resolvePublicOrigin: (configuredOrigin: string | undefined, authorityOrigin: string) => Effect.Effect<string, UnsafeLoopbackAddressError, never>;
|
|
66
|
+
/**
|
|
67
|
+
* The URL to open. The code rides in the fragment, which browsers do not send to servers and
|
|
68
|
+
* proxies do not log; the page strips it from the address bar once it has been spent.
|
|
69
|
+
*/
|
|
70
|
+
export declare const bootstrapUrl: (origin: string, code: Redacted.Redacted<PairingCode>) => string;
|
|
71
|
+
export interface OwnerSessionOptions {
|
|
72
|
+
/** Product name used in refusal messages, such as `"jcf-web"`. */
|
|
73
|
+
readonly product: string;
|
|
74
|
+
readonly cookieName: string;
|
|
75
|
+
/** `"csrf"` issues a CSRF token and admits same-origin writes; `"none"` refuses every unsafe method. */
|
|
76
|
+
readonly writes: "csrf" | "none";
|
|
77
|
+
/** Appended to the expiry refusal, such as `"restart jcf-web for a fresh URL"`. */
|
|
78
|
+
readonly freshUrlHint: string;
|
|
79
|
+
/** The origin this server considers itself to be. A request's Host header is never authoritative. */
|
|
80
|
+
readonly authorityOrigin: string;
|
|
81
|
+
/** The origin a browser request must carry: the authority, or a dev proxy that preserves Origin. */
|
|
82
|
+
readonly browserOrigin: string;
|
|
83
|
+
}
|
|
84
|
+
/** What a request presents. {@link OwnerSessionService.authorizeHttp} reads it from the HTTP request. */
|
|
85
|
+
export interface OwnerRequest {
|
|
86
|
+
readonly credential: string;
|
|
87
|
+
readonly csrfToken: string | undefined;
|
|
88
|
+
readonly fetchSite: string | undefined;
|
|
89
|
+
readonly method: string;
|
|
90
|
+
readonly origin: string | undefined;
|
|
91
|
+
}
|
|
92
|
+
export interface BootstrapRequest {
|
|
93
|
+
readonly authorization: string | undefined;
|
|
94
|
+
readonly origin: string | undefined;
|
|
95
|
+
}
|
|
96
|
+
/** Whether writes are possible, and the token they need when they are. Credential-bearing: holds the `CsrfToken`. */
|
|
97
|
+
export type WritePolicy = {
|
|
98
|
+
readonly _tag: "Csrf";
|
|
99
|
+
readonly token: Redacted.Redacted<CsrfToken>;
|
|
100
|
+
} | {
|
|
101
|
+
readonly _tag: "ReadOnly";
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* One process's Owner Session. Credential-bearing: it holds the `CsrfToken`, serializes the
|
|
105
|
+
* `SessionToken` into {@link OwnerSessionService.sessionCookie}, and mints `PairingCode`s; the
|
|
106
|
+
* session token and bootstrap state themselves stay private.
|
|
107
|
+
*/
|
|
108
|
+
export interface OwnerSessionService {
|
|
109
|
+
readonly authorityOrigin: string;
|
|
110
|
+
readonly browserOrigin: string;
|
|
111
|
+
readonly cookieName: string;
|
|
112
|
+
readonly writes: WritePolicy;
|
|
113
|
+
/** The `Set-Cookie` value carrying the session token. */
|
|
114
|
+
readonly sessionCookie: string;
|
|
115
|
+
/** Mint a one-time code, usable for a minute. Call only once the server is listening. */
|
|
116
|
+
readonly mintBootstrapCode: Effect.Effect<Redacted.Redacted<PairingCode>, BrowserPairingError>;
|
|
117
|
+
readonly authorizeRequest: (request: OwnerRequest) => Effect.Effect<void, OwnerSessionUnauthorizedError | OwnerSessionForbiddenError>;
|
|
118
|
+
/**
|
|
119
|
+
* {@link OwnerSessionService.authorizeRequest} for the current HTTP request, given the session
|
|
120
|
+
* cookie's value. API middleware maps the two errors onto the app's 401 and 403.
|
|
121
|
+
*/
|
|
122
|
+
readonly authorizeHttp: (credential: string) => Effect.Effect<void, OwnerSessionUnauthorizedError | OwnerSessionForbiddenError, HttpServerRequest.HttpServerRequest>;
|
|
123
|
+
/** Spend a bootstrap code. Succeeds at most once per minted code. */
|
|
124
|
+
readonly authorizeBootstrap: (request: BootstrapRequest) => Effect.Effect<void, OwnerSessionUnauthorizedError | OwnerSessionForbiddenError>;
|
|
125
|
+
}
|
|
126
|
+
declare const OwnerSession_base: Context.ServiceClass<OwnerSession, "@knpkv/browser-pairing/OwnerSession", OwnerSessionService>;
|
|
127
|
+
export declare class OwnerSession extends OwnerSession_base {
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Build an owner session with fresh tokens. No bootstrap code exists until
|
|
131
|
+
* {@link OwnerSessionService.mintBootstrapCode} runs.
|
|
132
|
+
*/
|
|
133
|
+
export declare const make: (options: OwnerSessionOptions) => Effect.Effect<OwnerSessionService, BrowserPairingError | CredentialCookieError | UnsafeLoopbackAddressError, Crypto.Crypto>;
|
|
134
|
+
export declare const layer: (options: OwnerSessionOptions) => Layer.Layer<OwnerSession, BrowserPairingError | CredentialCookieError | UnsafeLoopbackAddressError, Crypto.Crypto>;
|
|
135
|
+
/**
|
|
136
|
+
* The one route outside the authenticated API: `POST /auth/bootstrap` spends a code for the session
|
|
137
|
+
* cookie, answering `{ csrfToken }` when writes are allowed and `{}` otherwise.
|
|
138
|
+
*/
|
|
139
|
+
export declare const BootstrapRouter: Layer.Layer<never, never, HttpRouter.HttpRouter | OwnerSession | HttpRouter.Request<"Error", import("effect/http/HttpBody").HttpBodyError>>;
|
|
140
|
+
export {};
|
|
141
|
+
//# sourceMappingURL=owner-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owner-session.d.ts","sourceRoot":"","sources":["../src/owner-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who may talk to a single-operator loopback web app.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **One process, one operator.** The server binds a loopback address and mints a session token for
|
|
7
|
+
* itself at startup. There is no user table and no login: the person who can read the terminal is
|
|
8
|
+
* the person who gets in.
|
|
9
|
+
* - **The URL is the handshake.** A bootstrap code rides in a fragment the browser never sends
|
|
10
|
+
* upstream and is spent the first time it is exchanged for the session cookie. Codes come from
|
|
11
|
+
* {@link OwnerSessionService.mintBootstrapCode}, only once the server is listening; a newer code
|
|
12
|
+
* replaces an unspent one and reopens an exchange that guesses had closed.
|
|
13
|
+
* - **A read needs the cookie and must not be a browser cross-origin request; a write also needs the
|
|
14
|
+
* expected origin and the CSRF header.** Another page in the same browser can make the browser send
|
|
15
|
+
* a cookie, but Fetch Metadata keeps that page from starting reads without an Origin. An app with
|
|
16
|
+
* `writes: "none"` issues no CSRF token and refuses every unsafe method.
|
|
17
|
+
*
|
|
18
|
+
* Applications keep their HTTP API middleware and wire errors; they map
|
|
19
|
+
* {@link OwnerSessionUnauthorizedError} to 401 and {@link OwnerSessionForbiddenError} to 403.
|
|
20
|
+
* Multi-session, persisted authorization (Control Center) is out of scope.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import { Clock, Context, Crypto, Effect, Layer, Redacted, Ref, Schema } from "effect";
|
|
25
|
+
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/http";
|
|
26
|
+
import { credentialValuesEqual, expiresAt, issueCsrfToken, issuePairingCode, issueSessionToken } from "./crypto.js";
|
|
27
|
+
import { CredentialCookieError, decideOneTimeCredential, serializeCredentialCookie } from "./schema.js";
|
|
28
|
+
/** How long a minted bootstrap URL stays usable. Long enough to click, short enough to forget. */
|
|
29
|
+
const BOOTSTRAP_LIFETIME_MILLIS = 60_000;
|
|
30
|
+
/**
|
|
31
|
+
* Failed bootstrap exchanges before the exchange closes, until a new code is minted.
|
|
32
|
+
*
|
|
33
|
+
* The code is high-entropy, so this is not what makes guessing hard — it is what stops a process
|
|
34
|
+
* that is being guessed at from staying open all afternoon.
|
|
35
|
+
*/
|
|
36
|
+
const MAX_BOOTSTRAP_FAILURES = 5;
|
|
37
|
+
/** Methods that only read. They need the session, but no origin and no CSRF token. */
|
|
38
|
+
const safeMethods = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
39
|
+
const fetchSites = new Set(["cross-site", "none", "same-origin", "same-site"]);
|
|
40
|
+
/**
|
|
41
|
+
* The one origin other than the bound server that may be advertised: the Vite dev server that
|
|
42
|
+
* proxies the API and the bootstrap exchange.
|
|
43
|
+
*/
|
|
44
|
+
export const devPublicOrigin = "http://localhost:5173";
|
|
45
|
+
/** The session cookie or bootstrap code is missing, wrong, spent, or expired. Maps to 401. */
|
|
46
|
+
export class OwnerSessionUnauthorizedError extends Schema.TaggedError()("OwnerSessionUnauthorizedError", { message: Schema.String }) {
|
|
47
|
+
}
|
|
48
|
+
/** The credential is right but the request did not come from the owner's page. Maps to 403. */
|
|
49
|
+
export class OwnerSessionForbiddenError extends Schema.TaggedError()("OwnerSessionForbiddenError", { message: Schema.String }) {
|
|
50
|
+
}
|
|
51
|
+
/** A hostname or origin is not a plain HTTP loopback address. */
|
|
52
|
+
export class UnsafeLoopbackAddressError extends Schema.TaggedError()("UnsafeLoopbackAddressError", { address: Schema.String, message: Schema.String }) {
|
|
53
|
+
}
|
|
54
|
+
/** Hostnames a loopback web app may bind. */
|
|
55
|
+
export const LoopbackHostname = Schema.Literals(["127.0.0.1", "localhost", "::1"]);
|
|
56
|
+
/** Whether a URL hostname (which brackets IPv6) names the loopback interface. */
|
|
57
|
+
export const isLoopbackHostname = (hostname) => hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1" || hostname === "[::1]";
|
|
58
|
+
/** Refuse to bind anything a peer on the network could reach. */
|
|
59
|
+
export const requireLoopbackHostname = (hostname) => Schema.decodeUnknownEffect(LoopbackHostname)(hostname).pipe(Effect.mapError(() => new UnsafeLoopbackAddressError({ address: hostname, message: "Server may only listen on a loopback hostname" })));
|
|
60
|
+
/** The HTTP origin for a bound loopback server, without a trailing slash. */
|
|
61
|
+
export const loopbackOrigin = (hostname, port) => `http://${hostname === "::1" ? "[::1]" : hostname}:${port}`;
|
|
62
|
+
/** Validate an origin as plain HTTP on loopback with no path, query, or fragment. */
|
|
63
|
+
export const requireLoopbackOrigin = Effect.fn("OwnerSession.requireLoopbackOrigin")(function* (origin) {
|
|
64
|
+
const url = yield* Effect.try({
|
|
65
|
+
try: () => new URL(origin),
|
|
66
|
+
catch: () => new UnsafeLoopbackAddressError({ address: origin, message: "Not a valid HTTP origin" })
|
|
67
|
+
});
|
|
68
|
+
if (url.protocol !== "http:" ||
|
|
69
|
+
!isLoopbackHostname(url.hostname) ||
|
|
70
|
+
url.pathname !== "/" ||
|
|
71
|
+
url.search !== "" ||
|
|
72
|
+
url.hash !== "") {
|
|
73
|
+
return yield* new UnsafeLoopbackAddressError({
|
|
74
|
+
address: origin,
|
|
75
|
+
message: "Origin must be HTTP on a loopback host, without a path, query, or fragment"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return url.origin;
|
|
79
|
+
});
|
|
80
|
+
/** The origin to advertise: the bound server, or the dev server that proxies to it. */
|
|
81
|
+
export const resolvePublicOrigin = Effect.fn("OwnerSession.resolvePublicOrigin")(function* (configuredOrigin, authorityOrigin) {
|
|
82
|
+
const authority = yield* requireLoopbackOrigin(authorityOrigin);
|
|
83
|
+
if (configuredOrigin === undefined)
|
|
84
|
+
return authority;
|
|
85
|
+
const configured = yield* requireLoopbackOrigin(configuredOrigin);
|
|
86
|
+
if (configured !== authority && configured !== devPublicOrigin) {
|
|
87
|
+
return yield* new UnsafeLoopbackAddressError({
|
|
88
|
+
address: configuredOrigin,
|
|
89
|
+
message: "Public origin must be the bound server or its dev proxy"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return configured;
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* The URL to open. The code rides in the fragment, which browsers do not send to servers and
|
|
96
|
+
* proxies do not log; the page strips it from the address bar once it has been spent.
|
|
97
|
+
*/
|
|
98
|
+
export const bootstrapUrl = (origin, code) => `${origin.replace(/\/+$/u, "")}/#bootstrap_token=${encodeURIComponent(Redacted.value(code))}`;
|
|
99
|
+
export class OwnerSession extends Context.Service()("@knpkv/browser-pairing/OwnerSession") {
|
|
100
|
+
}
|
|
101
|
+
const isCredentialCookieError = Schema.is(CredentialCookieError);
|
|
102
|
+
const bearerToken = (authorization) => authorization !== undefined && authorization.startsWith("Bearer ")
|
|
103
|
+
? authorization.slice("Bearer ".length)
|
|
104
|
+
: undefined;
|
|
105
|
+
const requestFromHttp = (request, credential) => ({
|
|
106
|
+
credential,
|
|
107
|
+
csrfToken: request.headers["x-csrf-token"],
|
|
108
|
+
fetchSite: request.headers["sec-fetch-site"],
|
|
109
|
+
method: request.method,
|
|
110
|
+
origin: request.headers.origin
|
|
111
|
+
});
|
|
112
|
+
const makeService = (options, ownerToken, sessionCookie, writes, cryptoService, bootstrap, attempts) => {
|
|
113
|
+
const { browserOrigin, product } = options;
|
|
114
|
+
const unauthorized = (message) => new OwnerSessionUnauthorizedError({ message });
|
|
115
|
+
const forbidden = (message) => new OwnerSessionForbiddenError({ message });
|
|
116
|
+
const authorizeRequest = Effect.fn("OwnerSession.authorizeRequest")(function* (request) {
|
|
117
|
+
if (credentialValuesEqual(request.credential, Redacted.value(ownerToken)) !== true) {
|
|
118
|
+
return yield* unauthorized("Missing or invalid owner session");
|
|
119
|
+
}
|
|
120
|
+
const sameOrigin = request.origin !== undefined && request.origin === browserOrigin;
|
|
121
|
+
if (request.origin !== undefined && !sameOrigin) {
|
|
122
|
+
return yield* forbidden(`Request origin does not match this ${product} server`);
|
|
123
|
+
}
|
|
124
|
+
if (request.fetchSite !== undefined && !fetchSites.has(request.fetchSite)) {
|
|
125
|
+
return yield* forbidden("Request carries invalid browser site metadata");
|
|
126
|
+
}
|
|
127
|
+
if (request.fetchSite === "cross-site" ||
|
|
128
|
+
(!sameOrigin && (request.fetchSite === "same-site" || request.fetchSite === "none"))) {
|
|
129
|
+
return yield* forbidden(`Browser request is not from this ${product} page`);
|
|
130
|
+
}
|
|
131
|
+
// An explicit client such as curl carries neither Origin nor Fetch Metadata and remains allowed:
|
|
132
|
+
// it still has to present the process-scoped session cookie. Browser-marked cross-origin reads
|
|
133
|
+
// are rejected before their handler can start work.
|
|
134
|
+
if (safeMethods.has(request.method.toUpperCase()))
|
|
135
|
+
return;
|
|
136
|
+
if (writes._tag === "ReadOnly")
|
|
137
|
+
return yield* forbidden("This server only answers reads");
|
|
138
|
+
if (!sameOrigin)
|
|
139
|
+
return yield* forbidden(`Mutation origin does not match this ${product} server`);
|
|
140
|
+
const csrfMatches = request.csrfToken !== undefined &&
|
|
141
|
+
credentialValuesEqual(request.csrfToken, Redacted.value(writes.token)) === true;
|
|
142
|
+
if (!csrfMatches)
|
|
143
|
+
return yield* forbidden("Missing or invalid CSRF token");
|
|
144
|
+
});
|
|
145
|
+
const authorizeBootstrap = Effect.fn("OwnerSession.authorizeBootstrap")(function* (request) {
|
|
146
|
+
if (request.origin !== browserOrigin) {
|
|
147
|
+
return yield* forbidden(`Bootstrap origin does not match this ${product} server`);
|
|
148
|
+
}
|
|
149
|
+
const supplied = bearerToken(request.authorization);
|
|
150
|
+
const current = yield* Ref.get(bootstrap);
|
|
151
|
+
// Counted before the code is even compared, so a burst of parallel guesses cannot all pass the
|
|
152
|
+
// check together and then be counted afterwards.
|
|
153
|
+
const admit = Ref.modify(attempts, (state) => {
|
|
154
|
+
if (state.failedAttempts + state.inFlight >= MAX_BOOTSTRAP_FAILURES)
|
|
155
|
+
return ["unavailable", state];
|
|
156
|
+
const matches = supplied !== undefined && current !== undefined &&
|
|
157
|
+
credentialValuesEqual(supplied, Redacted.value(current.token)) === true;
|
|
158
|
+
if (!matches) {
|
|
159
|
+
return ["invalid", {
|
|
160
|
+
failedAttempts: Math.min(MAX_BOOTSTRAP_FAILURES, state.failedAttempts + 1),
|
|
161
|
+
inFlight: state.inFlight
|
|
162
|
+
}];
|
|
163
|
+
}
|
|
164
|
+
return ["accepted", { ...state, inFlight: state.inFlight + 1 }];
|
|
165
|
+
}).pipe(Effect.flatMap((admission) => admission === "unavailable"
|
|
166
|
+
? Effect.fail(unauthorized("Bootstrap is closed until a new code is minted"))
|
|
167
|
+
: admission === "invalid"
|
|
168
|
+
? Effect.fail(unauthorized("Missing or invalid bootstrap token"))
|
|
169
|
+
: Effect.void));
|
|
170
|
+
return yield* Effect.acquireUseRelease(admit, () => Effect.gen(function* () {
|
|
171
|
+
const now = yield* Clock.currentTimeMillis;
|
|
172
|
+
// Spent only if it is still the code that was compared: a newer mint replaced it otherwise.
|
|
173
|
+
const decision = yield* Ref.modify(bootstrap, (code) => {
|
|
174
|
+
if (code === undefined || current === undefined || code.token !== current.token) {
|
|
175
|
+
return ["consumed", code];
|
|
176
|
+
}
|
|
177
|
+
const next = decideOneTimeCredential({ consumedAt: code.available ? null : 0, expiresAt: code.expiresAt, revokedAt: null }, now);
|
|
178
|
+
return [next, next === "accepted" ? { ...code, available: false } : code];
|
|
179
|
+
});
|
|
180
|
+
if (decision === "accepted")
|
|
181
|
+
return;
|
|
182
|
+
return yield* unauthorized(decision === "expired"
|
|
183
|
+
? `Bootstrap token has expired — ${options.freshUrlHint}`
|
|
184
|
+
: decision === "consumed"
|
|
185
|
+
? "Bootstrap token has already been used"
|
|
186
|
+
: "Bootstrap token state is invalid");
|
|
187
|
+
}), () => Ref.update(attempts, (state) => ({ ...state, inFlight: Math.max(0, state.inFlight - 1) })));
|
|
188
|
+
});
|
|
189
|
+
const mintBootstrapCode = Effect.gen(function* () {
|
|
190
|
+
const token = yield* issuePairingCode();
|
|
191
|
+
const now = yield* Clock.currentTimeMillis;
|
|
192
|
+
const until = yield* expiresAt(now, BOOTSTRAP_LIFETIME_MILLIS);
|
|
193
|
+
yield* Ref.set(bootstrap, { token, expiresAt: until, available: true });
|
|
194
|
+
yield* Ref.set(attempts, { failedAttempts: 0, inFlight: 0 });
|
|
195
|
+
return token;
|
|
196
|
+
}).pipe(Effect.provideService(Crypto.Crypto, cryptoService), Effect.withSpan("OwnerSession.mintBootstrapCode"));
|
|
197
|
+
return {
|
|
198
|
+
authorityOrigin: options.authorityOrigin,
|
|
199
|
+
authorizeBootstrap,
|
|
200
|
+
authorizeHttp: (credential) => Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => authorizeRequest(requestFromHttp(request, credential))),
|
|
201
|
+
authorizeRequest,
|
|
202
|
+
browserOrigin,
|
|
203
|
+
cookieName: options.cookieName,
|
|
204
|
+
mintBootstrapCode,
|
|
205
|
+
sessionCookie,
|
|
206
|
+
writes
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Build an owner session with fresh tokens. No bootstrap code exists until
|
|
211
|
+
* {@link OwnerSessionService.mintBootstrapCode} runs.
|
|
212
|
+
*/
|
|
213
|
+
export const make = Effect.fn("OwnerSession.make")(function* (options) {
|
|
214
|
+
const authorityOrigin = yield* requireLoopbackOrigin(options.authorityOrigin);
|
|
215
|
+
const browserOrigin = yield* requireLoopbackOrigin(options.browserOrigin);
|
|
216
|
+
const cryptoService = yield* Crypto.Crypto;
|
|
217
|
+
const ownerToken = yield* issueSessionToken();
|
|
218
|
+
const writes = options.writes === "csrf"
|
|
219
|
+
? { _tag: "Csrf", token: yield* issueCsrfToken() }
|
|
220
|
+
: { _tag: "ReadOnly" };
|
|
221
|
+
// A cookie name is caller configuration: an invalid one fails typed. Anything else the serializer
|
|
222
|
+
// throws is a bug and stays a defect.
|
|
223
|
+
const sessionCookie = yield* Effect.sync(() => serializeCredentialCookie(ownerToken, {
|
|
224
|
+
httpOnly: true,
|
|
225
|
+
name: options.cookieName,
|
|
226
|
+
path: "/api",
|
|
227
|
+
sameSite: "strict",
|
|
228
|
+
secure: false
|
|
229
|
+
})).pipe(Effect.catchDefect((defect) => isCredentialCookieError(defect) ? Effect.fail(defect) : Effect.die(defect)));
|
|
230
|
+
return OwnerSession.of(makeService({ ...options, authorityOrigin, browserOrigin }, ownerToken, sessionCookie, writes, cryptoService, yield* Ref.make(undefined), yield* Ref.make({ failedAttempts: 0, inFlight: 0 })));
|
|
231
|
+
});
|
|
232
|
+
export const layer = (options) => Layer.effect(OwnerSession, make(options));
|
|
233
|
+
/**
|
|
234
|
+
* The one route outside the authenticated API: `POST /auth/bootstrap` spends a code for the session
|
|
235
|
+
* cookie, answering `{ csrfToken }` when writes are allowed and `{}` otherwise.
|
|
236
|
+
*/
|
|
237
|
+
export const BootstrapRouter = HttpRouter.use((router) => Effect.gen(function* () {
|
|
238
|
+
const session = yield* OwnerSession;
|
|
239
|
+
yield* router.add("POST", "/auth/bootstrap", Effect.gen(function* () {
|
|
240
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
241
|
+
const result = yield* Effect.result(session.authorizeBootstrap({ authorization: request.headers.authorization, origin: request.headers.origin }));
|
|
242
|
+
if (result._tag === "Failure") {
|
|
243
|
+
return HttpServerResponse.text(result.failure.message, {
|
|
244
|
+
status: result.failure._tag === "OwnerSessionUnauthorizedError" ? 401 : 403
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const body = session.writes._tag === "Csrf" ? { csrfToken: Redacted.value(session.writes.token) } : {};
|
|
248
|
+
return yield* HttpServerResponse.json(body, {
|
|
249
|
+
headers: { "cache-control": "no-store", "set-cookie": session.sessionCookie },
|
|
250
|
+
status: 200
|
|
251
|
+
});
|
|
252
|
+
}));
|
|
253
|
+
}));
|
|
254
|
+
//# sourceMappingURL=owner-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"owner-session.js","sourceRoot":"","sources":["../src/owner-session.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/browser-pairing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Typed browser pairing credentials and transport primitives",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"./schema": {
|
|
21
21
|
"types": "./dist/schema.d.ts",
|
|
22
22
|
"import": "./dist/schema.js"
|
|
23
|
+
},
|
|
24
|
+
"./owner-session": {
|
|
25
|
+
"types": "./dist/owner-session.d.ts",
|
|
26
|
+
"import": "./dist/owner-session.js"
|
|
23
27
|
}
|
|
24
28
|
},
|
|
25
29
|
"files": [
|