@voltro/plugin-auth 0.80.0 → 0.82.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.
@@ -0,0 +1,105 @@
1
+ import { Subject } from '@voltro/protocol';
2
+
3
+ /**
4
+ * Mint the verifier + state and build the authorize URL. Nothing is sent yet;
5
+ * the app opens `url` and waits for its redirect URI to be hit.
6
+ */
7
+ export declare const beginNativeLogin: (options: NativeLoginOptions) => Promise<NativeLoginHandoff>;
8
+
9
+ /** Trade the code + verifier for the session value. */
10
+ export declare const exchangeNativeCode: (input: {
11
+ readonly code: string;
12
+ readonly codeVerifier: string;
13
+ }, options: Pick<NativeLoginOptions, "apiBaseUrl" | "prefix"> & {
14
+ readonly fetch?: typeof fetch;
15
+ }) => Promise<NativeSession>;
16
+
17
+ export declare type NativeCallback = {
18
+ readonly kind: 'code';
19
+ readonly code: string;
20
+ } | {
21
+ readonly kind: 'error';
22
+ readonly error: string;
23
+ } | {
24
+ readonly kind: 'ignored';
25
+ readonly reason: 'not-the-callback' | 'state-mismatch' | 'no-code';
26
+ };
27
+
28
+ /** RFC 7636 S256 challenge for a verifier, through the injected hash. */
29
+ export declare const nativeCodeChallenge: (verifier: string, crypto: NativeCrypto) => Promise<string>;
30
+
31
+ /** The platform's entropy and hash. `randomBytes(n)` must be a CSPRNG. */
32
+ export declare interface NativeCrypto {
33
+ readonly randomBytes: (length: number) => Uint8Array;
34
+ readonly sha256: (bytes: Uint8Array) => Promise<Uint8Array>;
35
+ }
36
+
37
+ export declare class NativeExchangeError extends Error {
38
+ readonly status: number;
39
+ readonly code: string;
40
+ constructor(status: number, code: string);
41
+ }
42
+
43
+ export declare interface NativeLoginHandoff {
44
+ /** Open this in the SYSTEM browser (not an in-app webview): the user's
45
+ * existing browser session and password manager are the point. */
46
+ readonly url: string;
47
+ /** Keep both until the callback arrives — they are what makes the code
48
+ * usable by this app and this attempt only. */
49
+ readonly codeVerifier: string;
50
+ readonly state: string;
51
+ }
52
+
53
+ export declare interface NativeLoginOptions {
54
+ /** The api origin the `/auth/*` routes live on (`https://api.example.com`),
55
+ * or the web origin that proxies them. No trailing slash needed. */
56
+ readonly apiBaseUrl: string;
57
+ /** The URL the app listens on, byte for byte one of the plugin's
58
+ * `native.redirectUris`. */
59
+ readonly redirectUri: string;
60
+ readonly crypto: NativeCrypto;
61
+ /** The auth routes' prefix. Default `/auth`. */
62
+ readonly prefix?: string;
63
+ }
64
+
65
+ export declare interface NativeLoginRunner extends NativeLoginOptions {
66
+ /** Open the authorize URL in the system browser. */
67
+ readonly open: (url: string) => void | Promise<void>;
68
+ /** Resolve with the callback URL the OS hands the app (the first one that
69
+ * is this login's; see `parseNativeCallback`). */
70
+ readonly awaitCallback: (isOurs: (url: string) => NativeCallback) => Promise<NativeCallback>;
71
+ readonly fetch?: typeof fetch;
72
+ }
73
+
74
+ export declare interface NativeSession {
75
+ /** Present this on `x-voltro-session` — see {@link sessionHeaders}. */
76
+ readonly session: string;
77
+ /** Unix seconds the session expires. Probe `GET /auth/session` before then
78
+ * to receive a renewed value. */
79
+ readonly expiresAt: number;
80
+ readonly subject: Subject;
81
+ }
82
+
83
+ /**
84
+ * Read the callback the system browser sent the app to. `state` must match
85
+ * the handoff's; an unrelated URL (the app's ordinary deep links pass through
86
+ * the same listener) is `ignored`.
87
+ */
88
+ export declare const parseNativeCallback: (url: string, handoff: Pick<NativeLoginHandoff, "state">, redirectUri: string) => NativeCallback;
89
+
90
+ /**
91
+ * Probe `GET /auth/session` with a header-presented value. Answers the
92
+ * renewed value when the server rotated it, the same value otherwise, or
93
+ * `null` when the session no longer authenticates (sign the user in again).
94
+ */
95
+ export declare const probeNativeSession: (session: string, options: Pick<NativeLoginOptions, "apiBaseUrl" | "prefix"> & {
96
+ readonly fetch?: typeof fetch;
97
+ }) => Promise<NativeSession | null>;
98
+
99
+ /** One call: open the browser, wait for the callback, exchange the code. */
100
+ export declare const runNativeLogin: (runner: NativeLoginRunner) => Promise<NativeSession>;
101
+
102
+ /** The rpc headers for a session value — hand to `ResolvableHeaders`. */
103
+ export declare const sessionHeaders: (session: string) => Readonly<Record<string, string>>;
104
+
105
+ export { }
package/dist/native.js ADDED
@@ -0,0 +1,93 @@
1
+ import { SESSION_HEADER_NAME as e } from "@voltro/protocol";
2
+ //#region src/native/index.ts
3
+ var t = (e) => {
4
+ let t = "";
5
+ for (let n of e) t += String.fromCharCode(n);
6
+ return (typeof btoa == "function" ? btoa(t) : globalThis.Buffer.from(t, "binary").toString("base64")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
7
+ }, n = (e) => {
8
+ let t = new Uint8Array(e.length);
9
+ for (let n = 0; n < e.length; n++) t[n] = e.charCodeAt(n) & 127;
10
+ return t;
11
+ }, r = (e) => e.replace(/\/+$/, ""), i = async (e, r) => t(await r.sha256(n(e))), a = async (e) => {
12
+ let n = t(e.crypto.randomBytes(32)), a = t(e.crypto.randomBytes(16)), o = await i(n, e.crypto), s = new URLSearchParams({
13
+ redirect_uri: e.redirectUri,
14
+ code_challenge: o,
15
+ code_challenge_method: "S256",
16
+ state: a
17
+ }), c = e.prefix ?? "/auth";
18
+ return {
19
+ url: `${r(e.apiBaseUrl)}${c}/native/authorize?${s.toString()}`,
20
+ codeVerifier: n,
21
+ state: a
22
+ };
23
+ }, o = (e, t, n) => {
24
+ let i = e.indexOf("?");
25
+ if (r(i === -1 ? e : e.slice(0, i)) !== r(n)) return {
26
+ kind: "ignored",
27
+ reason: "not-the-callback"
28
+ };
29
+ let a = new URLSearchParams(i === -1 ? "" : e.slice(i + 1));
30
+ if (a.get("state") !== t.state) return {
31
+ kind: "ignored",
32
+ reason: "state-mismatch"
33
+ };
34
+ let o = a.get("error");
35
+ if (o) return {
36
+ kind: "error",
37
+ error: o
38
+ };
39
+ let s = a.get("code");
40
+ return s ? {
41
+ kind: "code",
42
+ code: s
43
+ } : {
44
+ kind: "ignored",
45
+ reason: "no-code"
46
+ };
47
+ }, s = class extends Error {
48
+ status;
49
+ code;
50
+ constructor(e, t) {
51
+ super(`native login exchange failed: ${t} (${e})`), this.name = "NativeExchangeError", this.status = e, this.code = t;
52
+ }
53
+ }, c = async (e, t) => {
54
+ let n = t.fetch ?? fetch, i = t.prefix ?? "/auth", a = await n(`${r(t.apiBaseUrl)}${i}/native/exchange`, {
55
+ method: "POST",
56
+ headers: { "content-type": "application/json" },
57
+ body: JSON.stringify(e)
58
+ }), o = await a.json().catch(() => ({}));
59
+ if (!a.ok || typeof o.session != "string" || typeof o.expiresAt != "number" || !o.subject) throw new s(a.status, o.error ?? "invalid_response");
60
+ return {
61
+ session: o.session,
62
+ expiresAt: o.expiresAt,
63
+ subject: o.subject
64
+ };
65
+ }, l = (t) => ({ [e]: t }), u = async (e, t) => {
66
+ let n = t.fetch ?? fetch, i = t.prefix ?? "/auth", a = await n(`${r(t.apiBaseUrl)}${i}/session`, {
67
+ method: "GET",
68
+ headers: l(e)
69
+ });
70
+ if (a.status === 401) return null;
71
+ let o = await a.json().catch(() => ({}));
72
+ return !a.ok || o.ok !== !0 || !o.subject || typeof o.expiresAt != "number" ? null : {
73
+ session: o.session ?? e,
74
+ expiresAt: o.expiresAt,
75
+ subject: o.subject
76
+ };
77
+ }, d = async (e) => {
78
+ let t = await a(e);
79
+ await e.open(t.url);
80
+ let n = await e.awaitCallback((n) => o(n, t, e.redirectUri));
81
+ if (n.kind === "error") throw new s(0, n.error);
82
+ if (n.kind !== "code") throw new s(0, n.reason);
83
+ return c({
84
+ code: n.code,
85
+ codeVerifier: t.codeVerifier
86
+ }, {
87
+ apiBaseUrl: e.apiBaseUrl,
88
+ ...e.prefix === void 0 ? {} : { prefix: e.prefix },
89
+ ...e.fetch === void 0 ? {} : { fetch: e.fetch }
90
+ });
91
+ };
92
+ //#endregion
93
+ export { s as NativeExchangeError, a as beginNativeLogin, c as exchangeNativeCode, i as nativeCodeChallenge, o as parseNativeCallback, u as probeNativeSession, d as runNativeLogin, l as sessionHeaders };