@molecule/app-auth 1.0.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/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Client-side authentication interface for molecule.dev.
3
+ *
4
+ * Provides a unified auth API that works across different auth strategies
5
+ * (JWT, session, OAuth, etc.): `createJWTAuthClient` builds a fetch-based
6
+ * client; `setClient`/`getClient` bond it; `login`/`logout`/`register`/
7
+ * `isAuthenticated`/`getUser` delegate to the bonded client. Framework
8
+ * bindings (e.g. `AuthProvider`/`useAuth` in `@molecule/app-react`) wrap the
9
+ * same client.
10
+ *
11
+ * @remarks
12
+ * - **A page reload "logging the user out" is the config default, not a bug.**
13
+ * Token storage defaults to `'memory'` (the secure default — a localStorage
14
+ * bearer token is XSS-exfiltratable). Staying signed in across reloads works
15
+ * by calling `client.initialize()` once at startup: it re-fetches
16
+ * `currentUserEndpoint` (default `/users/me`) with the httpOnly cookie the
17
+ * API set at login. Wire `initialize()`; do NOT "fix" reload-logout by
18
+ * copying tokens into localStorage.
19
+ * - **Endpoint defaults must match your API's real routes** (`/auth/login`,
20
+ * `/auth/register`, `/auth/refresh`, `/users/logout`, `/users/me`, …).
21
+ * Align them via `createJWTAuthClient({ baseURL, ...endpoints })` — never by
22
+ * hand-editing fetch calls in components.
23
+ * - Client-side auth state is UX only — the server enforces authorization on
24
+ * every request; hiding a screen is not protection.
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { createJWTAuthClient, setClient } from '@molecule/app-auth'
29
+ *
30
+ * const client = createJWTAuthClient({ baseURL: '/api' })
31
+ * setClient(client)
32
+ * await client.initialize() // restore the session from the httpOnly cookie
33
+ *
34
+ * // anywhere in the app
35
+ * import { getUser, isAuthenticated, login, logout } from '@molecule/app-auth'
36
+ * await login({ email, password })
37
+ * if (isAuthenticated()) console.log(getUser()?.email)
38
+ * ```
39
+ *
40
+ * @e2e
41
+ * Integration checklist — drive the real UI (live preview, no mocks), adapt
42
+ * each item to this app's actual screens/flows, and check every box off one
43
+ * by one. A box you can't check is an integration bug to fix — not a skip:
44
+ * - [ ] Logging in with VALID credentials on the real login screen resolves
45
+ * `login()` and flips AuthState to `authenticated: true` with `getUser()`
46
+ * returning the actual UserProfile — the UI shows that user's own `email` /
47
+ * `name` / `avatar`, never a placeholder or a different account.
48
+ * - [ ] A WRONG password shows a visible error (AuthState `error` is set and
49
+ * the promise rejects) and establishes NO session: `isAuthenticated()` stays
50
+ * false and no protected screen becomes reachable.
51
+ * - [ ] Registering through the sign-up form calls `register()`, creates the
52
+ * account, and lands authenticated (`authenticated: true`, `user` populated)
53
+ * on the app's post-signup screen — not back on the login form. If the flow
54
+ * shows an email-verification / "check your inbox" step, it must be genuinely
55
+ * backed (this client's `register()` returns an authenticated session
56
+ * directly and exposes no verify method) — never a dead screen the auth
57
+ * contract cannot advance past.
58
+ * - [ ] Logout calls `logout()` and clears the session (`authenticated: false`,
59
+ * `user: null`, tokens cleared); afterward every protected screen redirects
60
+ * back to login.
61
+ * - [ ] Visiting a protected route while logged OUT redirects to login (the
62
+ * guard reads `isAuthenticated()`), and after authenticating you land back on
63
+ * the originally-requested screen rather than a generic home.
64
+ * - [ ] The session survives a FULL page reload: after login, hard-reload →
65
+ * `initialize()` restores it from the httpOnly cookie (via
66
+ * `currentUserEndpoint`, default `/users/me`) and you stay signed in — you are
67
+ * NOT bounced to login. (The in-memory bearer token is dropped by design; the
68
+ * cookie restore is what keeps you in — do not "fix" this by writing the token
69
+ * to localStorage.)
70
+ * - [ ] Password reset round-trips: `requestPasswordReset({ email })` then
71
+ * `confirmPasswordReset({ token, password })` with the emailed token, after
72
+ * which `login()` with the NEW password succeeds and the OLD password no
73
+ * longer works.
74
+ * - [ ] Authorization holds: a signed-in user only ever reads/edits their OWN
75
+ * record (`getUser()` is the caller's profile, never another user's); the
76
+ * bearer token lives in memory only — it is never written to
77
+ * localStorage / sessionStorage where another script could read it; and an
78
+ * expired/invalid token fails closed — the app re-authenticates (`refresh()`
79
+ * or `logout()`), never silently serving another user's data.
80
+ *
81
+ * @module
82
+ */
83
+ export * from './client.js';
84
+ export * from './provider.js';
85
+ export * from './token.js';
86
+ export * from './types.js';
87
+ export * from './utilities.js';
88
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiFG;AAEH,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Auth client bond accessor.
3
+ *
4
+ * Bond packages call `setClient()` during setup. Application code uses
5
+ * `getClient()` to access login, logout, register, token management,
6
+ * and auth state subscription.
7
+ *
8
+ * @module
9
+ */
10
+ import type { AuthClient, UserProfile } from './types.js';
11
+ /**
12
+ * Registers an auth client as the active singleton. Called by bond
13
+ * packages during application startup.
14
+ *
15
+ * @param client - The auth client implementation to bond.
16
+ */
17
+ export declare const setClient: (client: AuthClient) => void;
18
+ /**
19
+ * Retrieves the bonded auth client, throwing if none is configured.
20
+ *
21
+ * @returns The bonded auth client.
22
+ * @throws {Error} If no auth client has been bonded.
23
+ */
24
+ export declare const getClient: <T extends UserProfile = UserProfile>() => AuthClient<T>;
25
+ /**
26
+ * Checks whether an auth client is currently bonded.
27
+ *
28
+ * @returns `true` if an auth client is bonded.
29
+ */
30
+ export declare const hasClient: () => boolean;
31
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAIzD;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,QAAQ,UAAU,KAAG,IAE9C,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,WAAW,GAAG,WAAW,OAAK,UAAU,CAAC,CAAC,CAE7E,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,QAAO,OAE5B,CAAA"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Auth client bond accessor.
3
+ *
4
+ * Bond packages call `setClient()` during setup. Application code uses
5
+ * `getClient()` to access login, logout, register, token management,
6
+ * and auth state subscription.
7
+ *
8
+ * @module
9
+ */
10
+ import { bond, isBonded, require as bondRequire } from '@molecule/app-bond';
11
+ const BOND_TYPE = 'auth-client';
12
+ /**
13
+ * Registers an auth client as the active singleton. Called by bond
14
+ * packages during application startup.
15
+ *
16
+ * @param client - The auth client implementation to bond.
17
+ */
18
+ export const setClient = (client) => {
19
+ bond(BOND_TYPE, client);
20
+ };
21
+ /**
22
+ * Retrieves the bonded auth client, throwing if none is configured.
23
+ *
24
+ * @returns The bonded auth client.
25
+ * @throws {Error} If no auth client has been bonded.
26
+ */
27
+ export const getClient = () => {
28
+ return bondRequire(BOND_TYPE);
29
+ };
30
+ /**
31
+ * Checks whether an auth client is currently bonded.
32
+ *
33
+ * @returns `true` if an auth client is bonded.
34
+ */
35
+ export const hasClient = () => {
36
+ return isBonded(BOND_TYPE);
37
+ };
38
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAI3E,MAAM,SAAS,GAAG,aAAa,CAAA;AAE/B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAkB,EAAQ,EAAE;IACpD,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;AACzB,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,GAAuD,EAAE;IAChF,OAAO,WAAW,CAAgB,SAAS,CAAC,CAAA;AAC9C,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,GAAY,EAAE;IACrC,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAA;AAC5B,CAAC,CAAA"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Token management utilities for authentication.
3
+ *
4
+ * Provides token storage, JWT parsing, and expiration checking.
5
+ *
6
+ * @module
7
+ */
8
+ import type { StorageAdapter, TokenStorage } from './types.js';
9
+ /**
10
+ * Creates a token storage implementation backed by either in-memory
11
+ * storage or a custom `StorageAdapter`.
12
+ *
13
+ * @param storage - `'memory'` for in-memory storage (lost on refresh),
14
+ * or a `StorageAdapter` for persistent storage.
15
+ * @param prefix - Key prefix for storage items (default: `'molecule:auth:'`).
16
+ * @returns A `TokenStorage` instance.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // In-memory storage (default)
21
+ * const storage = createTokenStorage('memory')
22
+ *
23
+ * // Custom storage adapter from @molecule/app-storage
24
+ * import { getProvider } from '@molecule/app-storage'
25
+ * const provider = getProvider()
26
+ * const storage = createTokenStorage({
27
+ * getItem: (key) => provider.get(key),
28
+ * setItem: (key, value) => provider.set(key, value),
29
+ * removeItem: (key) => provider.remove(key),
30
+ * })
31
+ * ```
32
+ */
33
+ export declare const createTokenStorage: (storage?: "memory" | StorageAdapter, prefix?: string) => TokenStorage;
34
+ /**
35
+ * Parses a JWT token payload without verification (base64-decodes
36
+ * the payload segment).
37
+ *
38
+ * @param token - The raw JWT string (header.payload.signature).
39
+ * @returns The decoded payload object, or `null` if parsing fails.
40
+ */
41
+ export declare const parseJWT: <T = Record<string, unknown>>(token: string) => T | null;
42
+ /**
43
+ * Checks if a JWT token is expired (or will expire within the buffer window).
44
+ *
45
+ * @param token - The raw JWT string.
46
+ * @param bufferSeconds - Seconds before actual expiry to consider the token expired (default: 0).
47
+ * @returns `true` if the token is expired or lacks an `exp` claim.
48
+ */
49
+ export declare const isTokenExpired: (token: string, bufferSeconds?: number) => boolean;
50
+ /**
51
+ * Returns the expiration timestamp of a JWT token in milliseconds.
52
+ *
53
+ * @param token - The raw JWT string.
54
+ * @returns The expiration time in ms since epoch, or `null` if the token has no `exp` claim.
55
+ */
56
+ export declare const getTokenExpiration: (token: string) => number | null;
57
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAe,MAAM,YAAY,CAAA;AAE3E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,kBAAkB,GAC7B,UAAS,QAAQ,GAAG,cAAyB,EAC7C,SAAQ,MAAyB,KAChC,YAoDF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,MAAM,KAAG,CAAC,GAAG,IAWzE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,gBAAe,MAAU,KAAG,OAMzE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,IAI3D,CAAA"}
package/dist/token.js ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Token management utilities for authentication.
3
+ *
4
+ * Provides token storage, JWT parsing, and expiration checking.
5
+ *
6
+ * @module
7
+ */
8
+ /**
9
+ * Creates a token storage implementation backed by either in-memory
10
+ * storage or a custom `StorageAdapter`.
11
+ *
12
+ * @param storage - `'memory'` for in-memory storage (lost on refresh),
13
+ * or a `StorageAdapter` for persistent storage.
14
+ * @param prefix - Key prefix for storage items (default: `'molecule:auth:'`).
15
+ * @returns A `TokenStorage` instance.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // In-memory storage (default)
20
+ * const storage = createTokenStorage('memory')
21
+ *
22
+ * // Custom storage adapter from @molecule/app-storage
23
+ * import { getProvider } from '@molecule/app-storage'
24
+ * const provider = getProvider()
25
+ * const storage = createTokenStorage({
26
+ * getItem: (key) => provider.get(key),
27
+ * setItem: (key, value) => provider.set(key, value),
28
+ * removeItem: (key) => provider.remove(key),
29
+ * })
30
+ * ```
31
+ */
32
+ export const createTokenStorage = (storage = 'memory', prefix = 'molecule:auth:') => {
33
+ const memoryStore = {};
34
+ // Determine storage adapter
35
+ let adapter = null;
36
+ if (typeof storage === 'object') {
37
+ adapter = storage;
38
+ }
39
+ const get = (key) => {
40
+ if (adapter) {
41
+ return adapter.getItem(prefix + key);
42
+ }
43
+ return memoryStore[key] ?? null;
44
+ };
45
+ const set = (key, value) => {
46
+ if (adapter) {
47
+ if (value === null) {
48
+ adapter.removeItem(prefix + key);
49
+ }
50
+ else {
51
+ adapter.setItem(prefix + key, value);
52
+ }
53
+ }
54
+ else {
55
+ memoryStore[key] = value;
56
+ }
57
+ };
58
+ return {
59
+ getAccessToken: () => get('accessToken'),
60
+ setAccessToken: (token) => set('accessToken', token),
61
+ getRefreshToken: () => get('refreshToken'),
62
+ setRefreshToken: (token) => set('refreshToken', token),
63
+ getUser: () => {
64
+ const data = get('user');
65
+ if (!data)
66
+ return null;
67
+ try {
68
+ return JSON.parse(data);
69
+ }
70
+ catch (_error) {
71
+ // Stored value is not valid JSON (corrupted/stale); returning null is safe
72
+ return null;
73
+ }
74
+ },
75
+ setUser: (user) => {
76
+ set('user', user ? JSON.stringify(user) : null);
77
+ },
78
+ clear: () => {
79
+ set('accessToken', null);
80
+ set('refreshToken', null);
81
+ set('user', null);
82
+ },
83
+ };
84
+ };
85
+ /**
86
+ * Parses a JWT token payload without verification (base64-decodes
87
+ * the payload segment).
88
+ *
89
+ * @param token - The raw JWT string (header.payload.signature).
90
+ * @returns The decoded payload object, or `null` if parsing fails.
91
+ */
92
+ export const parseJWT = (token) => {
93
+ try {
94
+ const parts = token.split('.');
95
+ if (parts.length !== 3)
96
+ return null;
97
+ const payload = parts[1];
98
+ const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
99
+ return JSON.parse(decoded);
100
+ }
101
+ catch (_error) {
102
+ // Malformed base64 or invalid JSON payload; returning null is the documented contract
103
+ return null;
104
+ }
105
+ };
106
+ /**
107
+ * Checks if a JWT token is expired (or will expire within the buffer window).
108
+ *
109
+ * @param token - The raw JWT string.
110
+ * @param bufferSeconds - Seconds before actual expiry to consider the token expired (default: 0).
111
+ * @returns `true` if the token is expired or lacks an `exp` claim.
112
+ */
113
+ export const isTokenExpired = (token, bufferSeconds = 0) => {
114
+ const payload = parseJWT(token);
115
+ if (!payload || !payload.exp)
116
+ return true;
117
+ const expiresAt = payload.exp * 1000;
118
+ const now = Date.now() + bufferSeconds * 1000;
119
+ return now >= expiresAt;
120
+ };
121
+ /**
122
+ * Returns the expiration timestamp of a JWT token in milliseconds.
123
+ *
124
+ * @param token - The raw JWT string.
125
+ * @returns The expiration time in ms since epoch, or `null` if the token has no `exp` claim.
126
+ */
127
+ export const getTokenExpiration = (token) => {
128
+ const payload = parseJWT(token);
129
+ if (!payload || !payload.exp)
130
+ return null;
131
+ return payload.exp * 1000;
132
+ };
133
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"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"}