@jamroast/jamauth-react 0.0.0-stage → 0.0.1

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,218 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties } from 'react';
3
+
4
+ /**
5
+ * Framework-agnostic OIDC authorization-code (PKCE) + embedded-iframe
6
+ * `postMessage` bridge for `@jamroast/jamauth-react`.
7
+ *
8
+ * Generalizes the PKCE S256 + `localStorage` token pattern from jamauth's own
9
+ * SPA (`web/src/lib/oidc-capture.ts`) so any client app (Next/Vite/Remix/etc.)
10
+ * can render a branded `SignIn`/`SignUp` on its own domain while every
11
+ * credential form (email+password, MFA, forgot/reset/verify) stays on the
12
+ * jamauth authority inside an embedded `<iframe>` that completes the OIDC
13
+ * authorization-code flow and relays `{ code, state }` to the parent via
14
+ * `postMessage`.
15
+ *
16
+ * Pure React + `window`/`postMessage`/`localStorage` with `typeof window`
17
+ * guards so it is SSR-safe. No credential inputs live here — credentials are
18
+ * typed only inside the jamauth frame (server-rendered interaction HTML).
19
+ */
20
+ declare const STORAGE_KEYS: {
21
+ readonly userToken: "jamauth.userToken";
22
+ readonly refreshToken: "jamauth.refreshToken";
23
+ readonly tokenExpiresIn: "jamauth.tokenExpiresIn";
24
+ readonly pkceVerifier: "jamauth.pkceVerifier";
25
+ };
26
+ /** Custom event fired whenever the stored session changes (sign-in/sign-out). */
27
+ declare const SESSION_CHANGED_EVENT = "jamauth:session-changed";
28
+ /** Caller-supplied OIDC client identity (never a hardcoded first-party default). */
29
+ interface OidcClientParams {
30
+ /** The jamauth issuer/authority origin, e.g. `https://jamauth.net`. */
31
+ authority: string;
32
+ clientId: string;
33
+ redirectUri: string;
34
+ /** RFC 8707 resource indicator; only needed for RS256 human-token minting. */
35
+ resource?: string;
36
+ /** OIDC `login_hint` — the invitee email, prefilled on sign-in. */
37
+ loginHint?: string;
38
+ /** OIDC scope; defaults to `openid profile email offline_access`. */
39
+ scope?: string;
40
+ /** Token endpoint override; defaults to `${authority}/token`. */
41
+ tokenEndpoint?: string;
42
+ /** OIDC `prompt` value (e.g. `'register'` for the sign-up surface). */
43
+ prompt?: string;
44
+ }
45
+ interface TokenResponse {
46
+ access_token: string;
47
+ refresh_token?: string;
48
+ expires_in?: number;
49
+ }
50
+ type AuthorizeMessage = {
51
+ type: 'jamauth:authorize';
52
+ code: string;
53
+ state: string;
54
+ };
55
+ type ReadyMessage = {
56
+ type: 'jamauth:ready';
57
+ };
58
+ /**
59
+ * Derive the configured authority origin. The handshake validates incoming
60
+ * `postMessage` events against this origin — never a hardcoded `jamauth.net`
61
+ * literal.
62
+ */
63
+ declare function authorityOrigin(authority: string): string;
64
+ /** Build the top-level `/auth` authorization URL (PKCE S256, `response_type=code`). */
65
+ declare function buildAuthorizeUrl(params: OidcClientParams): Promise<URL>;
66
+ /** Build the embedded `/embed/authorize` iframe URL (same PKCE S256 params). */
67
+ declare function buildEmbedUrl(params: OidcClientParams): Promise<URL>;
68
+ /**
69
+ * Validate an incoming `postMessage` event against the configured authority
70
+ * origin (never hardcoded) AND the iframe's `contentWindow` (so a same-origin
71
+ * event from an unexpected frame is ignored).
72
+ */
73
+ declare function isTrustedBridgeMessage(event: MessageEvent, authority: string, iframe: HTMLIFrameElement): boolean;
74
+ /** Parse a `jamauth:authorize` relay payload; returns `null` for other types. */
75
+ declare function parseAuthorizeMessage(data: unknown): {
76
+ code: string;
77
+ state: string;
78
+ } | null;
79
+ /** Parse a `jamauth:ready` handshake payload; returns `true` for the handshake. */
80
+ declare function isReadyMessage(data: unknown): data is ReadyMessage;
81
+ /** Store a minted token response (mirrors `oidc-capture.ts`). */
82
+ declare function storeTokenResponse(response: TokenResponse): void;
83
+ /** Clear the stored session tokens (sign-out). */
84
+ declare function clearStoredTokens(): void;
85
+ /**
86
+ * Exchange an authorization `code` for tokens via a PUBLIC PKCE request
87
+ * (`grant_type=authorization_code`, no client_secret, no Authorization header).
88
+ * The token endpoint defaults to `${authority}/token` (CORS-allowed for the
89
+ * registered client origin via `clientBasedCORS`).
90
+ */
91
+ declare function exchangeCodeForToken(params: OidcClientParams, code: string): Promise<TokenResponse>;
92
+ /**
93
+ * Fallback to a top-level redirect with a fresh PKCE `authorizeUrl`. Used when
94
+ * the embedded iframe is blocked (or fails to handshake) — idempotent and
95
+ * correct, so a false positive degrades to the top-level flow rather than a
96
+ * broken UX.
97
+ */
98
+ declare function fallbackToTopLevelRedirect(authorizeUrl: URL): void;
99
+
100
+ /** Configuration carried by `JamauthProvider`. */
101
+ interface JamauthConfig {
102
+ /** The jamauth issuer/authority origin, e.g. `https://jamauth.net`. */
103
+ authority: string;
104
+ clientId: string;
105
+ redirectUri: string;
106
+ resource?: string;
107
+ scope?: string;
108
+ tokenEndpoint?: string;
109
+ loginHint?: string;
110
+ /** Blocked-iframe handshake timeout in ms (default 5000). */
111
+ handshakeTimeoutMs?: number;
112
+ /** Called once the authorization code is exchanged for tokens. */
113
+ onToken?: (response: TokenResponse) => void;
114
+ }
115
+ /** Provide the jamauth authority + client identity to the embedded auth surface. */
116
+ declare function JamauthProvider({ config, children, }: {
117
+ config: JamauthConfig;
118
+ children: ReactNode;
119
+ }): react.JSX.Element;
120
+ /** Read the configured jamauth client identity (throws outside a provider). */
121
+ declare function useJamauth(): JamauthConfig;
122
+ interface SessionState {
123
+ isAuthenticated: boolean;
124
+ token: string | null;
125
+ }
126
+ /** Reactive session state sourced from the stored access token. */
127
+ declare function useSession(): SessionState;
128
+ /** High-level auth surface: session state plus sign-out. */
129
+ declare function useAuth(): SessionState & {
130
+ signOut: () => void;
131
+ };
132
+ /** Low-level OIDC bridge bound to the configured authority/client identity. */
133
+ declare function useOidc(): {
134
+ authorityOrigin: () => string;
135
+ buildAuthorizeUrl: () => Promise<URL>;
136
+ buildEmbedUrl: (prompt?: string) => Promise<URL>;
137
+ exchangeCodeForToken: (code: string) => Promise<TokenResponse>;
138
+ };
139
+ /** Renders `children` only while authenticated (access token present). */
140
+ declare function SignedIn({ children }: {
141
+ children: ReactNode;
142
+ }): react.JSX.Element | null;
143
+ /** Renders `children` only while signed out (no access token present). */
144
+ declare function SignedOut({ children }: {
145
+ children: ReactNode;
146
+ }): react.JSX.Element | null;
147
+
148
+ interface SignInProps {
149
+ /** Override the handshake timeout (ms) for this surface. */
150
+ handshakeTimeoutMs?: number;
151
+ className?: string;
152
+ style?: CSSProperties;
153
+ }
154
+ /**
155
+ * Embedded sign-in surface — an iframe wrapper around the jamauth
156
+ * `/embed/authorize` flow. This component renders **no credential inputs**:
157
+ * the email+password form (and MFA / forgot / reset / verify) lives on the
158
+ * jamauth authority inside the frame, and the completed authorization code is
159
+ * relayed back to the parent via `postMessage`.
160
+ */
161
+ declare function SignIn({ className, style }: SignInProps): react.JSX.Element;
162
+
163
+ interface SignUpProps {
164
+ /** Override the handshake timeout (ms) for this surface. */
165
+ handshakeTimeoutMs?: number;
166
+ className?: string;
167
+ style?: CSSProperties;
168
+ }
169
+ /**
170
+ * Embedded sign-up surface — an iframe wrapper around the jamauth
171
+ * `/embed/authorize` flow with `prompt=register`. Like `SignIn`, this renders
172
+ * **no credential inputs**; the sign-up form lives on the jamauth authority
173
+ * inside the frame and the completed authorization code is relayed back via
174
+ * `postMessage`.
175
+ */
176
+ declare function SignUp({ className, style }: SignUpProps): react.JSX.Element;
177
+
178
+ interface UserButtonProps {
179
+ /** Label shown when signed out (default "Sign in"). */
180
+ signInLabel?: ReactNode;
181
+ /** Label shown when signed in (default "Sign out"). */
182
+ signOutLabel?: ReactNode;
183
+ /** Called instead of the default sign-out when the user is signed in. */
184
+ onSignOut?: () => void;
185
+ className?: string;
186
+ style?: CSSProperties;
187
+ }
188
+ /**
189
+ * A minimal, framework-agnostic account button. It reflects the stored session
190
+ * state and, when signed in, signs the user out (or delegates to `onSignOut`).
191
+ * It renders no credential inputs.
192
+ */
193
+ declare function UserButton({ signInLabel, signOutLabel, onSignOut, className, style, }: UserButtonProps): react.JSX.Element;
194
+
195
+ type EmbeddedStatus = 'loading' | 'ready' | 'authenticated' | 'fallback';
196
+ interface EmbeddedAuthState {
197
+ iframeRef: React.RefObject<HTMLIFrameElement | null>;
198
+ src: string;
199
+ status: EmbeddedStatus;
200
+ }
201
+ /**
202
+ * Mount the embedded `/embed/authorize` iframe and drive the confirmation
203
+ * handshake + timeout fallback:
204
+ *
205
+ * - `iframe.onload` is NOT authoritative (a `frame-ancestors`-blocked frame
206
+ * still fires `onload`). Instead the parent validates
207
+ * `event.origin === authorityOrigin` (derived from the configured authority,
208
+ * never hardcoded) **and** `event.source === iframe.contentWindow`.
209
+ * - A `handshakeTimeoutMs` timer (default ~5s) falls back to a top-level
210
+ * `window.location.assign(freshPkceAuthorizeUrl)` when no `jamauth:ready`
211
+ * arrives — idempotent and correct, so a false positive degrades to the
212
+ * top-level flow rather than a broken UX.
213
+ * - On `jamauth:authorize`, the code is exchanged via public PKCE and the
214
+ * tokens are stored.
215
+ */
216
+ declare function useEmbeddedAuth(prompt?: string): EmbeddedAuthState;
217
+
218
+ export { type AuthorizeMessage, type EmbeddedAuthState, type EmbeddedStatus, type JamauthConfig, JamauthProvider, type OidcClientParams, type ReadyMessage, SESSION_CHANGED_EVENT, STORAGE_KEYS, type SessionState, SignIn, type SignInProps, SignUp, type SignUpProps, SignedIn, SignedOut, type TokenResponse, UserButton, type UserButtonProps, authorityOrigin, buildAuthorizeUrl, buildEmbedUrl, clearStoredTokens, exchangeCodeForToken, fallbackToTopLevelRedirect, isReadyMessage, isTrustedBridgeMessage, parseAuthorizeMessage, storeTokenResponse, useAuth, useEmbeddedAuth, useJamauth, useOidc, useSession };
@@ -0,0 +1,218 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties } from 'react';
3
+
4
+ /**
5
+ * Framework-agnostic OIDC authorization-code (PKCE) + embedded-iframe
6
+ * `postMessage` bridge for `@jamroast/jamauth-react`.
7
+ *
8
+ * Generalizes the PKCE S256 + `localStorage` token pattern from jamauth's own
9
+ * SPA (`web/src/lib/oidc-capture.ts`) so any client app (Next/Vite/Remix/etc.)
10
+ * can render a branded `SignIn`/`SignUp` on its own domain while every
11
+ * credential form (email+password, MFA, forgot/reset/verify) stays on the
12
+ * jamauth authority inside an embedded `<iframe>` that completes the OIDC
13
+ * authorization-code flow and relays `{ code, state }` to the parent via
14
+ * `postMessage`.
15
+ *
16
+ * Pure React + `window`/`postMessage`/`localStorage` with `typeof window`
17
+ * guards so it is SSR-safe. No credential inputs live here — credentials are
18
+ * typed only inside the jamauth frame (server-rendered interaction HTML).
19
+ */
20
+ declare const STORAGE_KEYS: {
21
+ readonly userToken: "jamauth.userToken";
22
+ readonly refreshToken: "jamauth.refreshToken";
23
+ readonly tokenExpiresIn: "jamauth.tokenExpiresIn";
24
+ readonly pkceVerifier: "jamauth.pkceVerifier";
25
+ };
26
+ /** Custom event fired whenever the stored session changes (sign-in/sign-out). */
27
+ declare const SESSION_CHANGED_EVENT = "jamauth:session-changed";
28
+ /** Caller-supplied OIDC client identity (never a hardcoded first-party default). */
29
+ interface OidcClientParams {
30
+ /** The jamauth issuer/authority origin, e.g. `https://jamauth.net`. */
31
+ authority: string;
32
+ clientId: string;
33
+ redirectUri: string;
34
+ /** RFC 8707 resource indicator; only needed for RS256 human-token minting. */
35
+ resource?: string;
36
+ /** OIDC `login_hint` — the invitee email, prefilled on sign-in. */
37
+ loginHint?: string;
38
+ /** OIDC scope; defaults to `openid profile email offline_access`. */
39
+ scope?: string;
40
+ /** Token endpoint override; defaults to `${authority}/token`. */
41
+ tokenEndpoint?: string;
42
+ /** OIDC `prompt` value (e.g. `'register'` for the sign-up surface). */
43
+ prompt?: string;
44
+ }
45
+ interface TokenResponse {
46
+ access_token: string;
47
+ refresh_token?: string;
48
+ expires_in?: number;
49
+ }
50
+ type AuthorizeMessage = {
51
+ type: 'jamauth:authorize';
52
+ code: string;
53
+ state: string;
54
+ };
55
+ type ReadyMessage = {
56
+ type: 'jamauth:ready';
57
+ };
58
+ /**
59
+ * Derive the configured authority origin. The handshake validates incoming
60
+ * `postMessage` events against this origin — never a hardcoded `jamauth.net`
61
+ * literal.
62
+ */
63
+ declare function authorityOrigin(authority: string): string;
64
+ /** Build the top-level `/auth` authorization URL (PKCE S256, `response_type=code`). */
65
+ declare function buildAuthorizeUrl(params: OidcClientParams): Promise<URL>;
66
+ /** Build the embedded `/embed/authorize` iframe URL (same PKCE S256 params). */
67
+ declare function buildEmbedUrl(params: OidcClientParams): Promise<URL>;
68
+ /**
69
+ * Validate an incoming `postMessage` event against the configured authority
70
+ * origin (never hardcoded) AND the iframe's `contentWindow` (so a same-origin
71
+ * event from an unexpected frame is ignored).
72
+ */
73
+ declare function isTrustedBridgeMessage(event: MessageEvent, authority: string, iframe: HTMLIFrameElement): boolean;
74
+ /** Parse a `jamauth:authorize` relay payload; returns `null` for other types. */
75
+ declare function parseAuthorizeMessage(data: unknown): {
76
+ code: string;
77
+ state: string;
78
+ } | null;
79
+ /** Parse a `jamauth:ready` handshake payload; returns `true` for the handshake. */
80
+ declare function isReadyMessage(data: unknown): data is ReadyMessage;
81
+ /** Store a minted token response (mirrors `oidc-capture.ts`). */
82
+ declare function storeTokenResponse(response: TokenResponse): void;
83
+ /** Clear the stored session tokens (sign-out). */
84
+ declare function clearStoredTokens(): void;
85
+ /**
86
+ * Exchange an authorization `code` for tokens via a PUBLIC PKCE request
87
+ * (`grant_type=authorization_code`, no client_secret, no Authorization header).
88
+ * The token endpoint defaults to `${authority}/token` (CORS-allowed for the
89
+ * registered client origin via `clientBasedCORS`).
90
+ */
91
+ declare function exchangeCodeForToken(params: OidcClientParams, code: string): Promise<TokenResponse>;
92
+ /**
93
+ * Fallback to a top-level redirect with a fresh PKCE `authorizeUrl`. Used when
94
+ * the embedded iframe is blocked (or fails to handshake) — idempotent and
95
+ * correct, so a false positive degrades to the top-level flow rather than a
96
+ * broken UX.
97
+ */
98
+ declare function fallbackToTopLevelRedirect(authorizeUrl: URL): void;
99
+
100
+ /** Configuration carried by `JamauthProvider`. */
101
+ interface JamauthConfig {
102
+ /** The jamauth issuer/authority origin, e.g. `https://jamauth.net`. */
103
+ authority: string;
104
+ clientId: string;
105
+ redirectUri: string;
106
+ resource?: string;
107
+ scope?: string;
108
+ tokenEndpoint?: string;
109
+ loginHint?: string;
110
+ /** Blocked-iframe handshake timeout in ms (default 5000). */
111
+ handshakeTimeoutMs?: number;
112
+ /** Called once the authorization code is exchanged for tokens. */
113
+ onToken?: (response: TokenResponse) => void;
114
+ }
115
+ /** Provide the jamauth authority + client identity to the embedded auth surface. */
116
+ declare function JamauthProvider({ config, children, }: {
117
+ config: JamauthConfig;
118
+ children: ReactNode;
119
+ }): react.JSX.Element;
120
+ /** Read the configured jamauth client identity (throws outside a provider). */
121
+ declare function useJamauth(): JamauthConfig;
122
+ interface SessionState {
123
+ isAuthenticated: boolean;
124
+ token: string | null;
125
+ }
126
+ /** Reactive session state sourced from the stored access token. */
127
+ declare function useSession(): SessionState;
128
+ /** High-level auth surface: session state plus sign-out. */
129
+ declare function useAuth(): SessionState & {
130
+ signOut: () => void;
131
+ };
132
+ /** Low-level OIDC bridge bound to the configured authority/client identity. */
133
+ declare function useOidc(): {
134
+ authorityOrigin: () => string;
135
+ buildAuthorizeUrl: () => Promise<URL>;
136
+ buildEmbedUrl: (prompt?: string) => Promise<URL>;
137
+ exchangeCodeForToken: (code: string) => Promise<TokenResponse>;
138
+ };
139
+ /** Renders `children` only while authenticated (access token present). */
140
+ declare function SignedIn({ children }: {
141
+ children: ReactNode;
142
+ }): react.JSX.Element | null;
143
+ /** Renders `children` only while signed out (no access token present). */
144
+ declare function SignedOut({ children }: {
145
+ children: ReactNode;
146
+ }): react.JSX.Element | null;
147
+
148
+ interface SignInProps {
149
+ /** Override the handshake timeout (ms) for this surface. */
150
+ handshakeTimeoutMs?: number;
151
+ className?: string;
152
+ style?: CSSProperties;
153
+ }
154
+ /**
155
+ * Embedded sign-in surface — an iframe wrapper around the jamauth
156
+ * `/embed/authorize` flow. This component renders **no credential inputs**:
157
+ * the email+password form (and MFA / forgot / reset / verify) lives on the
158
+ * jamauth authority inside the frame, and the completed authorization code is
159
+ * relayed back to the parent via `postMessage`.
160
+ */
161
+ declare function SignIn({ className, style }: SignInProps): react.JSX.Element;
162
+
163
+ interface SignUpProps {
164
+ /** Override the handshake timeout (ms) for this surface. */
165
+ handshakeTimeoutMs?: number;
166
+ className?: string;
167
+ style?: CSSProperties;
168
+ }
169
+ /**
170
+ * Embedded sign-up surface — an iframe wrapper around the jamauth
171
+ * `/embed/authorize` flow with `prompt=register`. Like `SignIn`, this renders
172
+ * **no credential inputs**; the sign-up form lives on the jamauth authority
173
+ * inside the frame and the completed authorization code is relayed back via
174
+ * `postMessage`.
175
+ */
176
+ declare function SignUp({ className, style }: SignUpProps): react.JSX.Element;
177
+
178
+ interface UserButtonProps {
179
+ /** Label shown when signed out (default "Sign in"). */
180
+ signInLabel?: ReactNode;
181
+ /** Label shown when signed in (default "Sign out"). */
182
+ signOutLabel?: ReactNode;
183
+ /** Called instead of the default sign-out when the user is signed in. */
184
+ onSignOut?: () => void;
185
+ className?: string;
186
+ style?: CSSProperties;
187
+ }
188
+ /**
189
+ * A minimal, framework-agnostic account button. It reflects the stored session
190
+ * state and, when signed in, signs the user out (or delegates to `onSignOut`).
191
+ * It renders no credential inputs.
192
+ */
193
+ declare function UserButton({ signInLabel, signOutLabel, onSignOut, className, style, }: UserButtonProps): react.JSX.Element;
194
+
195
+ type EmbeddedStatus = 'loading' | 'ready' | 'authenticated' | 'fallback';
196
+ interface EmbeddedAuthState {
197
+ iframeRef: React.RefObject<HTMLIFrameElement | null>;
198
+ src: string;
199
+ status: EmbeddedStatus;
200
+ }
201
+ /**
202
+ * Mount the embedded `/embed/authorize` iframe and drive the confirmation
203
+ * handshake + timeout fallback:
204
+ *
205
+ * - `iframe.onload` is NOT authoritative (a `frame-ancestors`-blocked frame
206
+ * still fires `onload`). Instead the parent validates
207
+ * `event.origin === authorityOrigin` (derived from the configured authority,
208
+ * never hardcoded) **and** `event.source === iframe.contentWindow`.
209
+ * - A `handshakeTimeoutMs` timer (default ~5s) falls back to a top-level
210
+ * `window.location.assign(freshPkceAuthorizeUrl)` when no `jamauth:ready`
211
+ * arrives — idempotent and correct, so a false positive degrades to the
212
+ * top-level flow rather than a broken UX.
213
+ * - On `jamauth:authorize`, the code is exchanged via public PKCE and the
214
+ * tokens are stored.
215
+ */
216
+ declare function useEmbeddedAuth(prompt?: string): EmbeddedAuthState;
217
+
218
+ export { type AuthorizeMessage, type EmbeddedAuthState, type EmbeddedStatus, type JamauthConfig, JamauthProvider, type OidcClientParams, type ReadyMessage, SESSION_CHANGED_EVENT, STORAGE_KEYS, type SessionState, SignIn, type SignInProps, SignUp, type SignUpProps, SignedIn, SignedOut, type TokenResponse, UserButton, type UserButtonProps, authorityOrigin, buildAuthorizeUrl, buildEmbedUrl, clearStoredTokens, exchangeCodeForToken, fallbackToTopLevelRedirect, isReadyMessage, isTrustedBridgeMessage, parseAuthorizeMessage, storeTokenResponse, useAuth, useEmbeddedAuth, useJamauth, useOidc, useSession };