@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/LICENSE +115 -0
- package/dist/client.d.ts +19 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +413 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +31 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +38 -0
- package/dist/provider.js.map +1 -0
- package/dist/token.d.ts +57 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +133 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +387 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities.d.ts +39 -0
- package/dist/utilities.d.ts.map +1 -0
- package/dist/utilities.js +39 -0
- package/dist/utilities.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work.
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
40
|
+
form, that is based on (or derived from) the Work and for which the
|
|
41
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
42
|
+
represent, as a whole, an original work of authorship.
|
|
43
|
+
|
|
44
|
+
"Contribution" shall mean any work of authorship, including the
|
|
45
|
+
original version of the Work and any modifications or additions
|
|
46
|
+
to that Work, that is intentionally submitted to the Licensor for
|
|
47
|
+
inclusion in the Work by the copyright owner or by an individual or
|
|
48
|
+
Legal Entity authorized to submit on behalf of the copyright owner.
|
|
49
|
+
|
|
50
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
51
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
52
|
+
subsequently incorporated within the Work.
|
|
53
|
+
|
|
54
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
55
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
56
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
57
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
58
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
59
|
+
Work and such Derivative Works in Source or Object form.
|
|
60
|
+
|
|
61
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
62
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
63
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
64
|
+
patent license to make, have made, use, offer to sell, sell, import,
|
|
65
|
+
and otherwise transfer the Work.
|
|
66
|
+
|
|
67
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
68
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
69
|
+
modifications, and in Source or Object form, provided that You
|
|
70
|
+
meet the following conditions:
|
|
71
|
+
|
|
72
|
+
(a) You must give any other recipients of the Work or
|
|
73
|
+
Derivative Works a copy of this License; and
|
|
74
|
+
|
|
75
|
+
(b) You must cause any modified files to carry prominent notices
|
|
76
|
+
stating that You changed the files; and
|
|
77
|
+
|
|
78
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
79
|
+
that You distribute, all copyright, patent, trademark, and
|
|
80
|
+
attribution notices from the Source form of the Work,
|
|
81
|
+
excluding those notices that do not pertain to any part of
|
|
82
|
+
the Derivative Works; and
|
|
83
|
+
|
|
84
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
85
|
+
distribution, then any Derivative Works that You distribute must
|
|
86
|
+
include a readable copy of the attribution notices contained
|
|
87
|
+
within such NOTICE file.
|
|
88
|
+
|
|
89
|
+
5. Submission of Contributions.
|
|
90
|
+
|
|
91
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
92
|
+
names, trademarks, service marks, or product names of the Licensor.
|
|
93
|
+
|
|
94
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
95
|
+
agreed to in writing, Licensor provides the Work on an "AS IS" BASIS,
|
|
96
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
|
|
97
|
+
|
|
98
|
+
8. Limitation of Liability. In no event and under no legal theory shall
|
|
99
|
+
any Contributor be liable to You for damages.
|
|
100
|
+
|
|
101
|
+
9. Accepting Warranty or Additional Liability.
|
|
102
|
+
|
|
103
|
+
Copyright 2026 Molecule Dev, Inc.
|
|
104
|
+
|
|
105
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
106
|
+
you may not use this file except in compliance with the License.
|
|
107
|
+
You may obtain a copy of the License at
|
|
108
|
+
|
|
109
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
110
|
+
|
|
111
|
+
Unless required by applicable law or agreed to in writing, software
|
|
112
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
113
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
114
|
+
See the License for the specific language governing permissions and
|
|
115
|
+
limitations under the License.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth client creation and management.
|
|
3
|
+
*
|
|
4
|
+
* Provides the JWT-based auth client implementation.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { AuthClient, AuthClientConfig, UserProfile } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Creates a simple JWT-based auth client.
|
|
11
|
+
*
|
|
12
|
+
* This is a basic implementation that can be extended or replaced
|
|
13
|
+
* with more sophisticated auth providers.
|
|
14
|
+
*
|
|
15
|
+
* @param config - Auth client configuration including endpoints, storage, and refresh settings.
|
|
16
|
+
* @returns A fully configured `AuthClient` instance.
|
|
17
|
+
*/
|
|
18
|
+
export declare const createJWTAuthClient: <T extends UserProfile = UserProfile>(config?: AuthClientConfig) => AuthClient<T>;
|
|
19
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAShB,WAAW,EACZ,MAAM,YAAY,CAAA;AA6BnB;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,GAAI,CAAC,SAAS,WAAW,GAAG,WAAW,EACrE,SAAQ,gBAAqB,KAC5B,UAAU,CAAC,CAAC,CAmbd,CAAA"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth client creation and management.
|
|
3
|
+
*
|
|
4
|
+
* Provides the JWT-based auth client implementation.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { I18nError } from '@molecule/app-i18n';
|
|
9
|
+
import { getLogger } from '@molecule/app-logger';
|
|
10
|
+
import { createTokenStorage, getTokenExpiration, isTokenExpired } from './token.js';
|
|
11
|
+
/**
|
|
12
|
+
* Whether this app is running inside a CROSS-SITE iframe — i.e. the molecule.dev
|
|
13
|
+
* live preview (the app at `127.0.0.1:<port>` embedded in the `localhost:3000`
|
|
14
|
+
* IDE). This mirrors the server's cross-site-preview decision (see
|
|
15
|
+
* `@molecule/api-resource-user` `getAuthCookieOptions`): in that case the server
|
|
16
|
+
* issues the `mol_auth` hint as `Secure; SameSite=None; Partitioned`, and a
|
|
17
|
+
* Partitioned cookie can only be deleted by a Set-Cookie / `document.cookie`
|
|
18
|
+
* write that ALSO carries Partitioned. A top-level document (the IDE itself, a
|
|
19
|
+
* normally-deployed app, or local `vite dev`) sets a plain `Lax` hint that the
|
|
20
|
+
* bare deletion already matches — so we must NOT append the cross-site attrs
|
|
21
|
+
* there (they would target the wrong cookie jar and miss the real cookie).
|
|
22
|
+
*
|
|
23
|
+
* Comparing `window.self !== window.top` is reference-only, so it never throws
|
|
24
|
+
* on a cross-origin embed.
|
|
25
|
+
*
|
|
26
|
+
* @returns `true` when embedded in a (cross-site) iframe, `false` at top level.
|
|
27
|
+
*/
|
|
28
|
+
const isCrossSitePreviewFrame = () => {
|
|
29
|
+
try {
|
|
30
|
+
return typeof window !== 'undefined' && window.self !== window.top;
|
|
31
|
+
}
|
|
32
|
+
catch (_error) {
|
|
33
|
+
// A cross-origin frame can, in rare engines, throw on `window.top` access —
|
|
34
|
+
// if we can't tell, assume top-level (the safe default matching a Lax hint).
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Creates a simple JWT-based auth client.
|
|
40
|
+
*
|
|
41
|
+
* This is a basic implementation that can be extended or replaced
|
|
42
|
+
* with more sophisticated auth providers.
|
|
43
|
+
*
|
|
44
|
+
* @param config - Auth client configuration including endpoints, storage, and refresh settings.
|
|
45
|
+
* @returns A fully configured `AuthClient` instance.
|
|
46
|
+
*/
|
|
47
|
+
export const createJWTAuthClient = (config = {}) => {
|
|
48
|
+
const { baseURL = '', loginEndpoint = '/auth/login', logoutEndpoint = '/users/logout', registerEndpoint = '/auth/register', refreshEndpoint = '/auth/refresh', profileEndpoint = '/auth/profile', forgotPasswordEndpoint = '/auth/password/reset', resetPasswordEndpoint = '/auth/password/reset/confirm', changePasswordEndpoint = '/auth/password/change', currentUserEndpoint = '/users/me', storagePrefix = 'molecule:auth:', storage = 'memory', autoRefresh = true, refreshBuffer = 60, } = config;
|
|
49
|
+
const logger = getLogger('auth');
|
|
50
|
+
const tokenStorage = createTokenStorage(storage, storagePrefix);
|
|
51
|
+
const stateListeners = new Set();
|
|
52
|
+
const eventListeners = new Set();
|
|
53
|
+
let refreshTimer = null;
|
|
54
|
+
let state = {
|
|
55
|
+
initialized: false,
|
|
56
|
+
authenticated: false,
|
|
57
|
+
user: null,
|
|
58
|
+
loading: false,
|
|
59
|
+
error: null,
|
|
60
|
+
};
|
|
61
|
+
const notify = () => {
|
|
62
|
+
stateListeners.forEach((callback) => callback(state));
|
|
63
|
+
};
|
|
64
|
+
const emitEvent = (event) => {
|
|
65
|
+
eventListeners.forEach((listener) => listener(event));
|
|
66
|
+
};
|
|
67
|
+
const setState = (updates) => {
|
|
68
|
+
state = { ...state, ...updates };
|
|
69
|
+
notify();
|
|
70
|
+
};
|
|
71
|
+
const scheduleRefresh = () => {
|
|
72
|
+
if (!autoRefresh)
|
|
73
|
+
return;
|
|
74
|
+
const token = tokenStorage.getAccessToken();
|
|
75
|
+
if (!token)
|
|
76
|
+
return;
|
|
77
|
+
const expiresAt = getTokenExpiration(token);
|
|
78
|
+
if (!expiresAt)
|
|
79
|
+
return;
|
|
80
|
+
const refreshTime = expiresAt - refreshBuffer * 1000 - Date.now();
|
|
81
|
+
if (refreshTime <= 0)
|
|
82
|
+
return;
|
|
83
|
+
if (refreshTimer) {
|
|
84
|
+
clearTimeout(refreshTimer);
|
|
85
|
+
}
|
|
86
|
+
refreshTimer = setTimeout(() => {
|
|
87
|
+
client.refresh().catch((err) => {
|
|
88
|
+
logger.warn('Token refresh failed, logging out', err);
|
|
89
|
+
client.logout();
|
|
90
|
+
});
|
|
91
|
+
}, refreshTime);
|
|
92
|
+
};
|
|
93
|
+
const fetchAPI = async (endpoint, options = {}) => {
|
|
94
|
+
const url = baseURL + endpoint;
|
|
95
|
+
const headers = {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
...options.headers,
|
|
98
|
+
};
|
|
99
|
+
const token = tokenStorage.getAccessToken();
|
|
100
|
+
if (token) {
|
|
101
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
102
|
+
}
|
|
103
|
+
// 'include' so httpOnly session cookies flow when the API is on another
|
|
104
|
+
// origin (fetch defaults to 'same-origin', which silently DROPS the login
|
|
105
|
+
// Set-Cookie cross-origin and breaks session restore). Safe both ways:
|
|
106
|
+
// same-origin behaves identically, and the fleet CORS bond pairs a strict
|
|
107
|
+
// origin allowlist with credentials:true. True cross-SITE deployments
|
|
108
|
+
// additionally need SameSite=None cookies — a server deployment concern.
|
|
109
|
+
const response = await fetch(url, {
|
|
110
|
+
credentials: 'include',
|
|
111
|
+
...options,
|
|
112
|
+
headers,
|
|
113
|
+
});
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
const body = await response.json().catch(() => ({}));
|
|
116
|
+
const key = body.errorKey ?? 'auth.error.requestFailed';
|
|
117
|
+
const fallback = body.error ?? body.message ?? 'Request failed';
|
|
118
|
+
throw new I18nError(key, undefined, fallback);
|
|
119
|
+
}
|
|
120
|
+
const data = await response.json();
|
|
121
|
+
// Compatibility shim: many @molecule/api-resource-* handlers (notably
|
|
122
|
+
// the user resource) wrap the entity under `props` rather than `user`
|
|
123
|
+
// (the auth-client's expected shape). Surface either so login/register
|
|
124
|
+
// populate state correctly regardless of the API's wrapper convention.
|
|
125
|
+
if (data && typeof data === 'object' && data.user === undefined && data.props !== undefined) {
|
|
126
|
+
data.user = data.props;
|
|
127
|
+
}
|
|
128
|
+
return data;
|
|
129
|
+
};
|
|
130
|
+
// [M1-1] Session restore from the httpOnly cookie. The bearer token is held
|
|
131
|
+
// in memory (a localStorage copy is XSS-exfiltratable, so it is forbidden)
|
|
132
|
+
// and is therefore lost on a full page load — but the httpOnly session cookie
|
|
133
|
+
// persists. Probe the current-user endpoint with credentials; if the cookie
|
|
134
|
+
// is still valid the server returns the user, and we re-establish the
|
|
135
|
+
// authenticated session WITHOUT the token ever being exposed to JavaScript.
|
|
136
|
+
//
|
|
137
|
+
// GATE on the non-httpOnly `mol_auth` presence hint (set by the server
|
|
138
|
+
// alongside the httpOnly token): only probe when a session plausibly exists.
|
|
139
|
+
// Without this gate, every anonymous caller would fire a guaranteed-401
|
|
140
|
+
// request (extra API call + console error). A forged hint only costs one
|
|
141
|
+
// harmless 401.
|
|
142
|
+
//
|
|
143
|
+
// Shared by initialize() (reload/deep-link restore) and refresh() (see the
|
|
144
|
+
// no-refresh-token fallback there).
|
|
145
|
+
const restoreFromSessionCookie = async () => {
|
|
146
|
+
const hasSessionHint = typeof document !== 'undefined' && /(?:^|;\s*)mol_auth=1(?:;|$)/.test(document.cookie);
|
|
147
|
+
if (!hasSessionHint)
|
|
148
|
+
return null;
|
|
149
|
+
try {
|
|
150
|
+
const result = await fetchAPI(currentUserEndpoint, {
|
|
151
|
+
method: 'GET',
|
|
152
|
+
credentials: 'include',
|
|
153
|
+
});
|
|
154
|
+
const restoredUser = (result?.user ?? null);
|
|
155
|
+
if (restoredUser) {
|
|
156
|
+
tokenStorage.setUser(restoredUser);
|
|
157
|
+
setState({ authenticated: true, user: restoredUser });
|
|
158
|
+
return restoredUser;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (_error) {
|
|
162
|
+
// No valid session cookie (401) or no current-user endpoint — remain
|
|
163
|
+
// logged out. This is an expected path for anonymous visitors.
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
};
|
|
167
|
+
const client = {
|
|
168
|
+
getState: () => state,
|
|
169
|
+
isAuthenticated: () => state.authenticated,
|
|
170
|
+
getUser: () => state.user,
|
|
171
|
+
setUser: (user) => {
|
|
172
|
+
tokenStorage.setUser(user);
|
|
173
|
+
setState({ user });
|
|
174
|
+
},
|
|
175
|
+
getAccessToken: () => tokenStorage.getAccessToken(),
|
|
176
|
+
setAccessToken: (token) => {
|
|
177
|
+
tokenStorage.setAccessToken(token);
|
|
178
|
+
},
|
|
179
|
+
getRefreshToken: () => tokenStorage.getRefreshToken(),
|
|
180
|
+
async login(credentials) {
|
|
181
|
+
setState({ loading: true, error: null });
|
|
182
|
+
try {
|
|
183
|
+
const result = await fetchAPI(loginEndpoint, {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
body: JSON.stringify(credentials),
|
|
186
|
+
});
|
|
187
|
+
// Two-factor gate: the server answered 206 `{ twoFactorRequired: true }`
|
|
188
|
+
// — no tokens/user issued. Surface the result WITHOUT marking the
|
|
189
|
+
// session authenticated; the caller re-prompts for the TOTP and retries.
|
|
190
|
+
if (result.twoFactorRequired) {
|
|
191
|
+
setState({ loading: false });
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
tokenStorage.setAccessToken(result.accessToken || null);
|
|
195
|
+
tokenStorage.setRefreshToken(result.refreshToken || null);
|
|
196
|
+
tokenStorage.setUser(result.user);
|
|
197
|
+
setState({
|
|
198
|
+
loading: false,
|
|
199
|
+
authenticated: true,
|
|
200
|
+
user: result.user,
|
|
201
|
+
});
|
|
202
|
+
logger.debug('User logged in');
|
|
203
|
+
emitEvent({ type: 'login', user: result.user });
|
|
204
|
+
scheduleRefresh();
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
const error = err instanceof Error ? err.message : 'Login failed';
|
|
209
|
+
setState({ loading: false, error });
|
|
210
|
+
emitEvent({ type: 'error', error });
|
|
211
|
+
throw err;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
async logout() {
|
|
215
|
+
setState({ loading: true });
|
|
216
|
+
try {
|
|
217
|
+
// [M1-1] The persistent credential is the httpOnly session cookie, not
|
|
218
|
+
// the in-memory bearer token. The bearer does not survive a page reload
|
|
219
|
+
// (after a reload `initialize()` restores `authenticated` from the cookie
|
|
220
|
+
// but cannot repopulate the in-memory token), so guarding the server
|
|
221
|
+
// logout on `token` alone meant a reloaded session could never log out
|
|
222
|
+
// server-side and the httpOnly cookie lived on. Call the endpoint
|
|
223
|
+
// whenever we believe a session exists — token OR restored-authenticated —
|
|
224
|
+
// and send `credentials` so the cookie authenticates the device
|
|
225
|
+
// revocation and the server clears the httpOnly cookie.
|
|
226
|
+
const token = tokenStorage.getAccessToken();
|
|
227
|
+
if (logoutEndpoint && (token || state.authenticated)) {
|
|
228
|
+
await fetchAPI(logoutEndpoint, { method: 'POST', credentials: 'include' }).catch((err) => {
|
|
229
|
+
logger.debug('Logout endpoint call failed (non-critical)', err);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
if (refreshTimer) {
|
|
235
|
+
clearTimeout(refreshTimer);
|
|
236
|
+
refreshTimer = null;
|
|
237
|
+
}
|
|
238
|
+
tokenStorage.clear();
|
|
239
|
+
// [M1-1] Drop the non-httpOnly session-presence hint so initialize()
|
|
240
|
+
// won't probe for cookie-restore on the next load even if the server
|
|
241
|
+
// logout call didn't complete (the server clears it too, on success).
|
|
242
|
+
// The deletion must MATCH the attributes the server SET the hint with, or
|
|
243
|
+
// it targets the wrong cookie jar and the stale hint survives. Only the
|
|
244
|
+
// cross-site live-preview iframe issues `mol_auth` as `Secure;
|
|
245
|
+
// SameSite=None; Partitioned` (a Partitioned cookie can only be deleted by
|
|
246
|
+
// a write that ALSO carries Partitioned); a top-level document sets a plain
|
|
247
|
+
// `Lax` hint the bare deletion already matches.
|
|
248
|
+
if (typeof document !== 'undefined') {
|
|
249
|
+
const previewAttrs = isCrossSitePreviewFrame()
|
|
250
|
+
? '; Secure; SameSite=None; Partitioned'
|
|
251
|
+
: '';
|
|
252
|
+
document.cookie = `mol_auth=; Max-Age=0; path=/${previewAttrs}`;
|
|
253
|
+
}
|
|
254
|
+
setState({
|
|
255
|
+
loading: false,
|
|
256
|
+
authenticated: false,
|
|
257
|
+
user: null,
|
|
258
|
+
});
|
|
259
|
+
logger.debug('User logged out');
|
|
260
|
+
emitEvent({ type: 'logout' });
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
async register(data) {
|
|
264
|
+
setState({ loading: true, error: null });
|
|
265
|
+
try {
|
|
266
|
+
const result = await fetchAPI(registerEndpoint, {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
body: JSON.stringify(data),
|
|
269
|
+
});
|
|
270
|
+
tokenStorage.setAccessToken(result.accessToken || null);
|
|
271
|
+
tokenStorage.setRefreshToken(result.refreshToken || null);
|
|
272
|
+
tokenStorage.setUser(result.user);
|
|
273
|
+
setState({
|
|
274
|
+
loading: false,
|
|
275
|
+
authenticated: true,
|
|
276
|
+
user: result.user,
|
|
277
|
+
});
|
|
278
|
+
emitEvent({ type: 'register', user: result.user });
|
|
279
|
+
scheduleRefresh();
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
const error = err instanceof Error ? err.message : 'Registration failed';
|
|
284
|
+
setState({ loading: false, error });
|
|
285
|
+
emitEvent({ type: 'error', error });
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
async refresh() {
|
|
290
|
+
const refreshToken = tokenStorage.getRefreshToken();
|
|
291
|
+
if (!refreshToken) {
|
|
292
|
+
// Cookie-session APIs (the @molecule/api-resource-user model) issue NO
|
|
293
|
+
// refresh token at all — login/register return only { accessToken, user }
|
|
294
|
+
// and the durable credential is the httpOnly session cookie. For them,
|
|
295
|
+
// "refresh" means re-deriving the session from that cookie, exactly like
|
|
296
|
+
// initialize()'s restore. Without this fallback every post-auth refresh
|
|
297
|
+
// (the auth modal's finishAuth, the upgrade-tab focus-return) threw
|
|
298
|
+
// 'No refresh token available' — which silently aborted the modal's
|
|
299
|
+
// signup/login flow right after the account was created.
|
|
300
|
+
const restoredUser = await restoreFromSessionCookie();
|
|
301
|
+
if (restoredUser) {
|
|
302
|
+
emitEvent({ type: 'refresh' });
|
|
303
|
+
return { user: restoredUser };
|
|
304
|
+
}
|
|
305
|
+
throw new I18nError('auth.error.noRefreshToken', undefined, 'No refresh token available');
|
|
306
|
+
}
|
|
307
|
+
const result = await fetchAPI(refreshEndpoint, {
|
|
308
|
+
method: 'POST',
|
|
309
|
+
body: JSON.stringify({ refreshToken }),
|
|
310
|
+
});
|
|
311
|
+
tokenStorage.setAccessToken(result.accessToken || null);
|
|
312
|
+
if (result.refreshToken) {
|
|
313
|
+
tokenStorage.setRefreshToken(result.refreshToken);
|
|
314
|
+
}
|
|
315
|
+
tokenStorage.setUser(result.user);
|
|
316
|
+
setState({
|
|
317
|
+
authenticated: true,
|
|
318
|
+
user: result.user,
|
|
319
|
+
});
|
|
320
|
+
emitEvent({ type: 'refresh' });
|
|
321
|
+
scheduleRefresh();
|
|
322
|
+
return result;
|
|
323
|
+
},
|
|
324
|
+
async requestPasswordReset(data) {
|
|
325
|
+
await fetchAPI(forgotPasswordEndpoint, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
body: JSON.stringify(data),
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
async confirmPasswordReset(data) {
|
|
331
|
+
await fetchAPI(resetPasswordEndpoint, {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
body: JSON.stringify(data),
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
async updateProfile(data) {
|
|
337
|
+
const result = await fetchAPI(profileEndpoint, {
|
|
338
|
+
method: 'PATCH',
|
|
339
|
+
body: JSON.stringify(data),
|
|
340
|
+
});
|
|
341
|
+
tokenStorage.setUser(result);
|
|
342
|
+
setState({ user: result });
|
|
343
|
+
return result;
|
|
344
|
+
},
|
|
345
|
+
async changePassword(oldPassword, newPassword) {
|
|
346
|
+
// Server handler (@molecule/api-resource-user `updatePassword`)
|
|
347
|
+
// reads `currentPassword`; legacy callers passed `oldPassword`,
|
|
348
|
+
// so accept either name in the hook signature but translate to
|
|
349
|
+
// the canonical `currentPassword` field when sending.
|
|
350
|
+
await fetchAPI(changePasswordEndpoint, {
|
|
351
|
+
method: 'POST',
|
|
352
|
+
body: JSON.stringify({ currentPassword: oldPassword, newPassword }),
|
|
353
|
+
});
|
|
354
|
+
},
|
|
355
|
+
async initialize() {
|
|
356
|
+
const token = tokenStorage.getAccessToken();
|
|
357
|
+
const user = tokenStorage.getUser();
|
|
358
|
+
if (token && user) {
|
|
359
|
+
if (!isTokenExpired(token)) {
|
|
360
|
+
setState({
|
|
361
|
+
initialized: true,
|
|
362
|
+
authenticated: true,
|
|
363
|
+
user,
|
|
364
|
+
});
|
|
365
|
+
scheduleRefresh();
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
// Token expired, try to refresh
|
|
369
|
+
const refreshToken = tokenStorage.getRefreshToken();
|
|
370
|
+
if (refreshToken) {
|
|
371
|
+
try {
|
|
372
|
+
await this.refresh();
|
|
373
|
+
setState({ initialized: true });
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
logger.warn('Initial token refresh failed', err);
|
|
378
|
+
tokenStorage.clear();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// [M1-1] Cookie-restore — this is what keeps a refresh / deep-link logged
|
|
383
|
+
// in under the in-memory-token model. See restoreFromSessionCookie above.
|
|
384
|
+
await restoreFromSessionCookie();
|
|
385
|
+
setState({ initialized: true });
|
|
386
|
+
},
|
|
387
|
+
subscribe(callback) {
|
|
388
|
+
stateListeners.add(callback);
|
|
389
|
+
return () => stateListeners.delete(callback);
|
|
390
|
+
},
|
|
391
|
+
onAuthChange(callback) {
|
|
392
|
+
stateListeners.add(callback);
|
|
393
|
+
return () => stateListeners.delete(callback);
|
|
394
|
+
},
|
|
395
|
+
getToken() {
|
|
396
|
+
return tokenStorage.getAccessToken();
|
|
397
|
+
},
|
|
398
|
+
addEventListener(listener) {
|
|
399
|
+
eventListeners.add(listener);
|
|
400
|
+
return () => eventListeners.delete(listener);
|
|
401
|
+
},
|
|
402
|
+
destroy() {
|
|
403
|
+
if (refreshTimer) {
|
|
404
|
+
clearTimeout(refreshTimer);
|
|
405
|
+
refreshTimer = null;
|
|
406
|
+
}
|
|
407
|
+
stateListeners.clear();
|
|
408
|
+
eventListeners.clear();
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
return client;
|
|
412
|
+
};
|
|
413
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
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.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","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"}
|