@atlasauth/svelte 0.1.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 +21 -0
- package/README.md +231 -0
- package/dist/accessors.d.ts +81 -0
- package/dist/accessors.js +126 -0
- package/dist/appearance.d.ts +85 -0
- package/dist/appearance.js +91 -0
- package/dist/components/AtlasLoaded.svelte +7 -0
- package/dist/components/AtlasLoaded.svelte.d.ts +18 -0
- package/dist/components/AtlasLoading.svelte +7 -0
- package/dist/components/AtlasLoading.svelte.d.ts +18 -0
- package/dist/components/AtlasProvider.svelte +23 -0
- package/dist/components/AtlasProvider.svelte.d.ts +33 -0
- package/dist/components/AttemptFlow.svelte +95 -0
- package/dist/components/AttemptFlow.svelte.d.ts +30 -0
- package/dist/components/OrganizationProfile.svelte +22 -0
- package/dist/components/OrganizationProfile.svelte.d.ts +16 -0
- package/dist/components/OrganizationSwitcher.svelte +25 -0
- package/dist/components/OrganizationSwitcher.svelte.d.ts +16 -0
- package/dist/components/Protect.svelte +21 -0
- package/dist/components/Protect.svelte.d.ts +32 -0
- package/dist/components/SignIn.svelte +16 -0
- package/dist/components/SignIn.svelte.d.ts +22 -0
- package/dist/components/SignUp.svelte +16 -0
- package/dist/components/SignUp.svelte.d.ts +21 -0
- package/dist/components/SignedIn.svelte +7 -0
- package/dist/components/SignedIn.svelte.d.ts +18 -0
- package/dist/components/SignedOut.svelte +7 -0
- package/dist/components/SignedOut.svelte.d.ts +18 -0
- package/dist/components/UserButton.svelte +40 -0
- package/dist/components/UserButton.svelte.d.ts +16 -0
- package/dist/components/UserProfile.svelte +35 -0
- package/dist/components/UserProfile.svelte.d.ts +16 -0
- package/dist/context.d.ts +105 -0
- package/dist/context.js +258 -0
- package/dist/fapi.d.ts +11 -0
- package/dist/fapi.js +11 -0
- package/dist/i18n.d.ts +23 -0
- package/dist/i18n.js +77 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +35 -0
- package/dist/protect.d.ts +18 -0
- package/dist/protect.js +18 -0
- package/dist/server.d.ts +101 -0
- package/dist/server.js +87 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type Readable } from 'svelte/store';
|
|
2
|
+
import { type ProviderToken } from '@atlasauth/js';
|
|
3
|
+
import { type Catalog } from './i18n';
|
|
4
|
+
import type { Appearance } from './appearance';
|
|
5
|
+
import type { AtlasOrganizationMembership, AtlasSession, AtlasUser, SessionClaims } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* §10.1 the Svelte peer of React's `<AtlasProvider>`.
|
|
8
|
+
*
|
|
9
|
+
* React installs the auth engine through a context provider component and hands
|
|
10
|
+
* state out via hooks. The Svelte idiom is the same shape turned inside out: the
|
|
11
|
+
* engine is a plain controller whose reactive state is exposed as Svelte
|
|
12
|
+
* **stores**, installed into component context by {@link initAtlas} so the
|
|
13
|
+
* accessors ({@link useAtlas}, {@link getUser}, …) can `getContext` it back.
|
|
14
|
+
*
|
|
15
|
+
* It holds exactly one thing that matters: the token, in memory. Not
|
|
16
|
+
* localStorage, not a cookie this code writes — the refresh token is HttpOnly so
|
|
17
|
+
* browser script never touches it, and the short-lived JWT lives only as long as
|
|
18
|
+
* the tab. Persisting either would survive the tab and become the thing an XSS
|
|
19
|
+
* reads first.
|
|
20
|
+
*/
|
|
21
|
+
export type AuthStatus = 'loading' | 'signed_in' | 'signed_out';
|
|
22
|
+
/** The context key. A `Symbol` so it cannot collide with an app's own context. */
|
|
23
|
+
export declare const ATLAS_KEY: unique symbol;
|
|
24
|
+
export interface AtlasInitOptions {
|
|
25
|
+
publishableKey: string;
|
|
26
|
+
/** The instance's FAPI origin. Empty for same-origin. */
|
|
27
|
+
frontendApi?: string;
|
|
28
|
+
appearance?: Appearance;
|
|
29
|
+
localization?: Catalog;
|
|
30
|
+
fetchImpl?: typeof fetch;
|
|
31
|
+
/**
|
|
32
|
+
* Skip the automatic browser boot (redirect handling + `/v1/client` load).
|
|
33
|
+
* Useful in tests, or when the caller wants to drive `boot()` by hand.
|
|
34
|
+
*/
|
|
35
|
+
manualBoot?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The reactive surface. Every field is a Svelte store, so `$user`, `$session`,
|
|
39
|
+
* `$isSignedIn` work in a `.svelte` template and `get(user)` works in plain TS.
|
|
40
|
+
*/
|
|
41
|
+
export interface AtlasStores {
|
|
42
|
+
/** `'loading' | 'signed_in' | 'signed_out'`. */
|
|
43
|
+
status: Readable<AuthStatus>;
|
|
44
|
+
user: Readable<AtlasUser | null>;
|
|
45
|
+
session: Readable<AtlasSession | null>;
|
|
46
|
+
claims: Readable<SessionClaims | null>;
|
|
47
|
+
memberships: Readable<AtlasOrganizationMembership[]>;
|
|
48
|
+
/** `status === 'signed_in'`. */
|
|
49
|
+
isSignedIn: Readable<boolean>;
|
|
50
|
+
/** `status !== 'loading'` — the guard against a flash of signed-out content. */
|
|
51
|
+
isLoaded: Readable<boolean>;
|
|
52
|
+
}
|
|
53
|
+
export interface AtlasClient extends AtlasStores {
|
|
54
|
+
publishableKey: string;
|
|
55
|
+
frontendApi: string;
|
|
56
|
+
appearance?: Appearance;
|
|
57
|
+
catalog: Catalog;
|
|
58
|
+
/**
|
|
59
|
+
* §5.3: a sign-in attempt handed back by an OAuth redirect that still needs a
|
|
60
|
+
* second factor (no session was issued). Null in the ordinary case. `<SignIn>`
|
|
61
|
+
* seeds itself from this so the user resumes at the 2FA step.
|
|
62
|
+
*/
|
|
63
|
+
pendingAttempt: {
|
|
64
|
+
id: string;
|
|
65
|
+
status: string;
|
|
66
|
+
} | null;
|
|
67
|
+
getToken(): Promise<string | null>;
|
|
68
|
+
/**
|
|
69
|
+
* §6.6 the signed-in user's OWN live token at a connected provider (Google,
|
|
70
|
+
* GitHub, …). Null when there is no usable token. The refresh token is never
|
|
71
|
+
* exposed; the server refreshes a stale token on read.
|
|
72
|
+
*/
|
|
73
|
+
getProviderToken(provider: string): Promise<ProviderToken | null>;
|
|
74
|
+
signOut(): Promise<void>;
|
|
75
|
+
setActiveOrganization(organizationId: string | null): Promise<void>;
|
|
76
|
+
reload(): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Redirect handling + the first `/v1/client` load. Runs once, in the browser
|
|
79
|
+
* only. {@link initAtlas} calls it automatically unless `manualBoot` is set;
|
|
80
|
+
* `<AtlasProvider>` also calls it in `onMount`, and a second call is a no-op.
|
|
81
|
+
*/
|
|
82
|
+
boot(): Promise<void>;
|
|
83
|
+
/** Cancel the scheduled refresh — call from a component's `onDestroy`. */
|
|
84
|
+
destroy(): void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build the auth controller. Framework-agnostic aside from the Svelte stores it
|
|
88
|
+
* exposes; {@link initAtlas} is the thin wrapper that also `setContext`s it.
|
|
89
|
+
*/
|
|
90
|
+
export declare function createAtlasClient(options: AtlasInitOptions): AtlasClient;
|
|
91
|
+
/**
|
|
92
|
+
* Install the Atlas client into Svelte component context and return it.
|
|
93
|
+
*
|
|
94
|
+
* Call from the `<script>` of a top-level component (or `<AtlasProvider>`), the
|
|
95
|
+
* peer of mounting React's `<AtlasProvider>`. Everything below in the tree can
|
|
96
|
+
* then reach the stores and actions through {@link useAtlas} and friends.
|
|
97
|
+
*/
|
|
98
|
+
export declare function initAtlas(options: AtlasInitOptions): AtlasClient;
|
|
99
|
+
/**
|
|
100
|
+
* The installed client, from any descendant of a component that called
|
|
101
|
+
* {@link initAtlas}. Throws rather than returning a null-ish default: a store of
|
|
102
|
+
* `null` outside the provider is indistinguishable from a signed-out user, and
|
|
103
|
+
* the developer spends an afternoon on it.
|
|
104
|
+
*/
|
|
105
|
+
export declare function useAtlas(): AtlasClient;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
import { derived, get, writable } from 'svelte/store';
|
|
3
|
+
import { FapiClient, TokenCache, getProviderToken as fetchProviderToken, msUntilRefresh, readExpiry, readRedirectResult, stripRedirectParams, } from '@atlasauth/js';
|
|
4
|
+
import { EN_US, withFallback } from './i18n';
|
|
5
|
+
/** The context key. A `Symbol` so it cannot collide with an app's own context. */
|
|
6
|
+
export const ATLAS_KEY = Symbol('atlas');
|
|
7
|
+
function decodeClaims(jwt) {
|
|
8
|
+
const parts = jwt.split('.');
|
|
9
|
+
if (parts.length !== 3)
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the auth controller. Framework-agnostic aside from the Svelte stores it
|
|
20
|
+
* exposes; {@link initAtlas} is the thin wrapper that also `setContext`s it.
|
|
21
|
+
*/
|
|
22
|
+
export function createAtlasClient(options) {
|
|
23
|
+
const { publishableKey } = options;
|
|
24
|
+
const baseUrl = options.frontendApi ?? '';
|
|
25
|
+
const doFetch = options.fetchImpl ?? (typeof fetch !== 'undefined' ? fetch : undefined);
|
|
26
|
+
const catalog = options.localization ? withFallback(options.localization) : EN_US;
|
|
27
|
+
const status = writable('loading');
|
|
28
|
+
const user = writable(null);
|
|
29
|
+
const session = writable(null);
|
|
30
|
+
const claims = writable(null);
|
|
31
|
+
const memberships = writable([]);
|
|
32
|
+
const cache = new TokenCache();
|
|
33
|
+
let timer = null;
|
|
34
|
+
let booted = false;
|
|
35
|
+
/**
|
|
36
|
+
* Captured synchronously at construction (not after boot) so it is in context
|
|
37
|
+
* before `<SignIn>` reads it — a needs_second_factor redirect carries a status
|
|
38
|
+
* but no ticket, and that is what the 2FA resume seeds from.
|
|
39
|
+
*/
|
|
40
|
+
const pendingAttempt = (() => {
|
|
41
|
+
if (typeof window === 'undefined')
|
|
42
|
+
return null;
|
|
43
|
+
const result = readRedirectResult(window.location.search);
|
|
44
|
+
return result?.status && !result.ticket
|
|
45
|
+
? { id: result.attemptId, status: result.status }
|
|
46
|
+
: null;
|
|
47
|
+
})();
|
|
48
|
+
async function call(path, init) {
|
|
49
|
+
if (!doFetch)
|
|
50
|
+
throw new Error('No fetch implementation available.');
|
|
51
|
+
return doFetch(`${baseUrl}${path}`, {
|
|
52
|
+
...init,
|
|
53
|
+
// Cookies carry the session; a fetch without this looks identical to being
|
|
54
|
+
// signed out.
|
|
55
|
+
credentials: 'include',
|
|
56
|
+
headers: {
|
|
57
|
+
'x-publishable-key': publishableKey,
|
|
58
|
+
...(init?.body ? { 'content-type': 'application/json' } : {}),
|
|
59
|
+
...init?.headers,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function applyToken(jwt, sessionId) {
|
|
64
|
+
const expiresAt = readExpiry(jwt);
|
|
65
|
+
if (!expiresAt)
|
|
66
|
+
return;
|
|
67
|
+
cache.set({ jwt, expiresAt, sessionId });
|
|
68
|
+
claims.set(decodeClaims(jwt));
|
|
69
|
+
}
|
|
70
|
+
function clearSession() {
|
|
71
|
+
cache.clear();
|
|
72
|
+
status.set('signed_out');
|
|
73
|
+
user.set(null);
|
|
74
|
+
session.set(null);
|
|
75
|
+
claims.set(null);
|
|
76
|
+
memberships.set([]);
|
|
77
|
+
}
|
|
78
|
+
function scheduleRefresh() {
|
|
79
|
+
if (timer)
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
timer = null;
|
|
82
|
+
if (get(status) !== 'signed_in')
|
|
83
|
+
return;
|
|
84
|
+
const token = cache.peek();
|
|
85
|
+
if (!token)
|
|
86
|
+
return;
|
|
87
|
+
// §7.4: proactive, jittered refresh scheduled from the token's own expiry.
|
|
88
|
+
const delay = msUntilRefresh({ token, now: Date.now() });
|
|
89
|
+
timer = setTimeout(() => void refresh(), delay);
|
|
90
|
+
}
|
|
91
|
+
async function reload() {
|
|
92
|
+
try {
|
|
93
|
+
const response = await call('/v1/client');
|
|
94
|
+
if (!response.ok)
|
|
95
|
+
throw new Error('boot failed');
|
|
96
|
+
const body = (await response.json());
|
|
97
|
+
if (!body.session || !body.user) {
|
|
98
|
+
clearSession();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
user.set(body.user);
|
|
102
|
+
session.set(body.session);
|
|
103
|
+
memberships.set(body.organization_memberships ?? []);
|
|
104
|
+
if (body.jwt)
|
|
105
|
+
applyToken(body.jwt, body.session.id);
|
|
106
|
+
status.set('signed_in');
|
|
107
|
+
scheduleRefresh();
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/**
|
|
111
|
+
* A failed boot is NOT signed-out. Rendering a sign-in form because the
|
|
112
|
+
* network hiccuped throws away a perfectly good session and makes the user
|
|
113
|
+
* re-authenticate for no reason.
|
|
114
|
+
*/
|
|
115
|
+
status.update((current) => (current === 'loading' ? 'signed_out' : current));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function refresh() {
|
|
119
|
+
const current = cache.peek();
|
|
120
|
+
if (!current)
|
|
121
|
+
return;
|
|
122
|
+
try {
|
|
123
|
+
const response = await call(`/v1/client/sessions/${current.sessionId}/tokens`, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
});
|
|
126
|
+
if (!response.ok)
|
|
127
|
+
throw new Error('refresh failed');
|
|
128
|
+
const body = (await response.json());
|
|
129
|
+
applyToken(body.jwt, body.session_id);
|
|
130
|
+
scheduleRefresh();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// The session may genuinely be gone. Re-boot rather than guess.
|
|
134
|
+
await reload();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function getToken() {
|
|
138
|
+
// A 5s skew so a token that would die inside the caller's request is
|
|
139
|
+
// refreshed first rather than handed over.
|
|
140
|
+
const usable = cache.get(5_000);
|
|
141
|
+
if (usable)
|
|
142
|
+
return usable.jwt;
|
|
143
|
+
await refresh();
|
|
144
|
+
return cache.get(5_000)?.jwt ?? null;
|
|
145
|
+
}
|
|
146
|
+
const fapiClient = new FapiClient({ publishableKey, baseUrl, fetchImpl: doFetch });
|
|
147
|
+
function getProviderToken(provider) {
|
|
148
|
+
return fetchProviderToken(fapiClient, provider);
|
|
149
|
+
}
|
|
150
|
+
async function signOut() {
|
|
151
|
+
const current = cache.peek();
|
|
152
|
+
if (current) {
|
|
153
|
+
await call(`/v1/client/sessions/${current.sessionId}/revoke`, { method: 'POST' }).catch(() => undefined);
|
|
154
|
+
}
|
|
155
|
+
if (timer)
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
timer = null;
|
|
158
|
+
clearSession();
|
|
159
|
+
}
|
|
160
|
+
async function setActiveOrganization(organizationId) {
|
|
161
|
+
const current = cache.peek();
|
|
162
|
+
if (!current)
|
|
163
|
+
return;
|
|
164
|
+
const response = await call(`/v1/client/sessions/${current.sessionId}/touch`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
body: JSON.stringify({ active_organization_id: organizationId }),
|
|
167
|
+
});
|
|
168
|
+
if (!response.ok)
|
|
169
|
+
return;
|
|
170
|
+
const body = (await response.json());
|
|
171
|
+
// The new token carries the new org claims; re-boot would work too but costs
|
|
172
|
+
// a round trip for information already in hand.
|
|
173
|
+
applyToken(body.jwt, current.sessionId);
|
|
174
|
+
await reload();
|
|
175
|
+
}
|
|
176
|
+
async function boot() {
|
|
177
|
+
if (booted)
|
|
178
|
+
return;
|
|
179
|
+
booted = true;
|
|
180
|
+
/**
|
|
181
|
+
* §6.3 step 3: complete a redirect-based sign-in. An OAuth or hosted-page
|
|
182
|
+
* flow lands back here with a one-time ticket in the URL; exchange it for
|
|
183
|
+
* cookies, scrub the URL so a reload can't replay it, THEN boot. A redirect
|
|
184
|
+
* that only carries a `__atlas_status` (a second factor is still owed) has no
|
|
185
|
+
* ticket, so nothing is exchanged and the app boots signed-out.
|
|
186
|
+
*/
|
|
187
|
+
const result = typeof window !== 'undefined' ? readRedirectResult(window.location.search) : null;
|
|
188
|
+
if (result?.ticket) {
|
|
189
|
+
await call('/v1/client/tickets/exchange', {
|
|
190
|
+
method: 'POST',
|
|
191
|
+
body: JSON.stringify({ attempt_id: result.attemptId, ticket: result.ticket }),
|
|
192
|
+
}).catch(() => undefined);
|
|
193
|
+
}
|
|
194
|
+
if (result && typeof window !== 'undefined') {
|
|
195
|
+
window.history.replaceState({}, document.title, stripRedirectParams(window.location.href));
|
|
196
|
+
}
|
|
197
|
+
await reload();
|
|
198
|
+
}
|
|
199
|
+
function destroy() {
|
|
200
|
+
if (timer)
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
timer = null;
|
|
203
|
+
}
|
|
204
|
+
const isSignedIn = derived(status, ($status) => $status === 'signed_in');
|
|
205
|
+
const isLoaded = derived(status, ($status) => $status !== 'loading');
|
|
206
|
+
const client = {
|
|
207
|
+
publishableKey,
|
|
208
|
+
frontendApi: baseUrl,
|
|
209
|
+
appearance: options.appearance,
|
|
210
|
+
catalog,
|
|
211
|
+
pendingAttempt,
|
|
212
|
+
status: { subscribe: status.subscribe },
|
|
213
|
+
user: { subscribe: user.subscribe },
|
|
214
|
+
session: { subscribe: session.subscribe },
|
|
215
|
+
claims: { subscribe: claims.subscribe },
|
|
216
|
+
memberships: { subscribe: memberships.subscribe },
|
|
217
|
+
isSignedIn,
|
|
218
|
+
isLoaded,
|
|
219
|
+
getToken,
|
|
220
|
+
getProviderToken,
|
|
221
|
+
signOut,
|
|
222
|
+
setActiveOrganization,
|
|
223
|
+
reload,
|
|
224
|
+
boot,
|
|
225
|
+
destroy,
|
|
226
|
+
};
|
|
227
|
+
// Kick off the browser boot immediately unless the caller opts out. Guarded to
|
|
228
|
+
// the browser so SvelteKit SSR never touches window / a credentialed fetch.
|
|
229
|
+
if (!options.manualBoot && typeof window !== 'undefined') {
|
|
230
|
+
void boot();
|
|
231
|
+
}
|
|
232
|
+
return client;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Install the Atlas client into Svelte component context and return it.
|
|
236
|
+
*
|
|
237
|
+
* Call from the `<script>` of a top-level component (or `<AtlasProvider>`), the
|
|
238
|
+
* peer of mounting React's `<AtlasProvider>`. Everything below in the tree can
|
|
239
|
+
* then reach the stores and actions through {@link useAtlas} and friends.
|
|
240
|
+
*/
|
|
241
|
+
export function initAtlas(options) {
|
|
242
|
+
const client = createAtlasClient(options);
|
|
243
|
+
setContext(ATLAS_KEY, client);
|
|
244
|
+
return client;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The installed client, from any descendant of a component that called
|
|
248
|
+
* {@link initAtlas}. Throws rather than returning a null-ish default: a store of
|
|
249
|
+
* `null` outside the provider is indistinguishable from a signed-out user, and
|
|
250
|
+
* the developer spends an afternoon on it.
|
|
251
|
+
*/
|
|
252
|
+
export function useAtlas() {
|
|
253
|
+
const client = getContext(ATLAS_KEY);
|
|
254
|
+
if (!client) {
|
|
255
|
+
throw new Error('Atlas accessors must be used under a component that called initAtlas().');
|
|
256
|
+
}
|
|
257
|
+
return client;
|
|
258
|
+
}
|
package/dist/fapi.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FAPI client and sign-in flow driver now live in `@atlasauth/js` — the pure,
|
|
3
|
+
* framework-agnostic browser FAPI flow — so the React `<SignIn/>` and the
|
|
4
|
+
* framework-agnostic `@atlasauth/embed` widget drive the exact same endpoints
|
|
5
|
+
* through one implementation and cannot diverge in what the server sees.
|
|
6
|
+
*
|
|
7
|
+
* This module is kept as a thin re-export so every existing importer inside the
|
|
8
|
+
* React SDK (`./components`, the public barrel, the tests) keeps resolving the
|
|
9
|
+
* driver from `./fapi` unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export { FapiClient, advance, prepareFactor, pollAttempt, shouldKeepPolling, requestForStep, flowFromPending, initialFlow, getProviderToken, type FapiClientOptions, type FapiResponse, type FlowState, type ProviderToken, } from '@atlasauth/js';
|
package/dist/fapi.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The FAPI client and sign-in flow driver now live in `@atlasauth/js` — the pure,
|
|
3
|
+
* framework-agnostic browser FAPI flow — so the React `<SignIn/>` and the
|
|
4
|
+
* framework-agnostic `@atlasauth/embed` widget drive the exact same endpoints
|
|
5
|
+
* through one implementation and cannot diverge in what the server sees.
|
|
6
|
+
*
|
|
7
|
+
* This module is kept as a thin re-export so every existing importer inside the
|
|
8
|
+
* React SDK (`./components`, the public barrel, the tests) keeps resolving the
|
|
9
|
+
* driver from `./fapi` unchanged.
|
|
10
|
+
*/
|
|
11
|
+
export { FapiClient, advance, prepareFactor, pollAttempt, shouldKeepPolling, requestForStep, flowFromPending, initialFlow, getProviderToken, } from '@atlasauth/js';
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §10.3: "Every string passes through an i18n catalog; en-US ships, catalog
|
|
3
|
+
* format documented for community locales."
|
|
4
|
+
*
|
|
5
|
+
* A flat key-to-string map rather than nested objects, because nesting looks
|
|
6
|
+
* tidier and makes a missing key a runtime crash on `undefined.of.a.chain`
|
|
7
|
+
* rather than a visible fallback.
|
|
8
|
+
*
|
|
9
|
+
* A missing key renders the KEY, not an empty string. A blank button is a bug
|
|
10
|
+
* nobody can report; a button reading `signIn.submit` is one anyone can.
|
|
11
|
+
*/
|
|
12
|
+
export type Catalog = Record<string, string>;
|
|
13
|
+
export declare const EN_US: Catalog;
|
|
14
|
+
/**
|
|
15
|
+
* Look up a key and interpolate `{placeholders}`.
|
|
16
|
+
*
|
|
17
|
+
* An unknown placeholder is left visible for the same reason a missing key is:
|
|
18
|
+
* `Continue with {provider}` in the UI is a bug report waiting to be filed,
|
|
19
|
+
* whereas `Continue with ` is one nobody notices until a customer does.
|
|
20
|
+
*/
|
|
21
|
+
export declare function translate(catalog: Catalog, key: string, variables?: Record<string, string>): string;
|
|
22
|
+
/** Merge a community locale over en-US so a partial translation still renders. */
|
|
23
|
+
export declare function withFallback(locale: Catalog): Catalog;
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §10.3: "Every string passes through an i18n catalog; en-US ships, catalog
|
|
3
|
+
* format documented for community locales."
|
|
4
|
+
*
|
|
5
|
+
* A flat key-to-string map rather than nested objects, because nesting looks
|
|
6
|
+
* tidier and makes a missing key a runtime crash on `undefined.of.a.chain`
|
|
7
|
+
* rather than a visible fallback.
|
|
8
|
+
*
|
|
9
|
+
* A missing key renders the KEY, not an empty string. A blank button is a bug
|
|
10
|
+
* nobody can report; a button reading `signIn.submit` is one anyone can.
|
|
11
|
+
*/
|
|
12
|
+
export const EN_US = {
|
|
13
|
+
'signIn.title': 'Sign in',
|
|
14
|
+
'signIn.subtitle': 'to continue to {application}',
|
|
15
|
+
'signIn.identifierLabel': 'Email address',
|
|
16
|
+
'signIn.passwordLabel': 'Password',
|
|
17
|
+
'signIn.submit': 'Continue',
|
|
18
|
+
'signIn.forgotPassword': 'Forgot password?',
|
|
19
|
+
'signIn.noAccount': 'No account?',
|
|
20
|
+
'signIn.signUpLink': 'Sign up',
|
|
21
|
+
'signIn.orDivider': 'or',
|
|
22
|
+
'signIn.socialButton': 'Continue with {provider}',
|
|
23
|
+
'signIn.emailCodeLabel': 'Verification code',
|
|
24
|
+
'signIn.magicLinkSent': 'Check your email for a sign-in link.',
|
|
25
|
+
'signIn.checkOtherTab': 'You can close this tab — you are signed in where you started.',
|
|
26
|
+
'signUp.title': 'Create your account',
|
|
27
|
+
'signUp.submit': 'Continue',
|
|
28
|
+
'signUp.haveAccount': 'Already have an account?',
|
|
29
|
+
'signUp.signInLink': 'Sign in',
|
|
30
|
+
'mfa.title': 'Two-step verification',
|
|
31
|
+
'mfa.subtitle': 'Enter the code from your authenticator app.',
|
|
32
|
+
'mfa.codeLabel': 'Code',
|
|
33
|
+
'mfa.enrollFirstCodeLabel': 'Code from your authenticator app',
|
|
34
|
+
'mfa.enrollSecondCodeLabel': 'The next code it shows',
|
|
35
|
+
'mfa.usePasskey': 'Use a passkey instead',
|
|
36
|
+
'mfa.useBackupCode': 'Use a recovery code',
|
|
37
|
+
'mfa.submit': 'Verify',
|
|
38
|
+
'reset.title': 'Reset your password',
|
|
39
|
+
'reset.submit': 'Send reset code',
|
|
40
|
+
'reset.newPasswordLabel': 'New password',
|
|
41
|
+
'userButton.manageAccount': 'Manage account',
|
|
42
|
+
'userButton.signOut': 'Sign out',
|
|
43
|
+
'userButton.signOutAll': 'Sign out of all devices',
|
|
44
|
+
'userProfile.title': 'Account',
|
|
45
|
+
'userProfile.profileSection': 'Profile',
|
|
46
|
+
'userProfile.emailsSection': 'Email addresses',
|
|
47
|
+
'userProfile.securitySection': 'Security',
|
|
48
|
+
'userProfile.devicesSection': 'Active devices',
|
|
49
|
+
'userProfile.dangerSection': 'Danger zone',
|
|
50
|
+
'userProfile.addEmail': 'Add an email address',
|
|
51
|
+
'userProfile.makePrimary': 'Make primary',
|
|
52
|
+
'userProfile.unverified': 'Unverified',
|
|
53
|
+
'userProfile.revokeDevice': 'Sign out',
|
|
54
|
+
'userProfile.deleteAccount': 'Delete account',
|
|
55
|
+
'organization.switcher': 'Switch organization',
|
|
56
|
+
'organization.personal': 'Personal account',
|
|
57
|
+
'organization.members': 'Members',
|
|
58
|
+
'organization.invite': 'Invite member',
|
|
59
|
+
'organization.roleLabel': 'Role',
|
|
60
|
+
'error.generic': 'Something went wrong. Please try again.',
|
|
61
|
+
'error.network': 'We could not reach the server. Check your connection.',
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Look up a key and interpolate `{placeholders}`.
|
|
65
|
+
*
|
|
66
|
+
* An unknown placeholder is left visible for the same reason a missing key is:
|
|
67
|
+
* `Continue with {provider}` in the UI is a bug report waiting to be filed,
|
|
68
|
+
* whereas `Continue with ` is one nobody notices until a customer does.
|
|
69
|
+
*/
|
|
70
|
+
export function translate(catalog, key, variables = {}) {
|
|
71
|
+
const template = catalog[key] ?? EN_US[key] ?? key;
|
|
72
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => variables[name] ?? match);
|
|
73
|
+
}
|
|
74
|
+
/** Merge a community locale over en-US so a partial translation still renders. */
|
|
75
|
+
export function withFallback(locale) {
|
|
76
|
+
return { ...EN_US, ...locale };
|
|
77
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@atlasauth/svelte` — the official Svelte / SvelteKit SDK for Atlas.
|
|
3
|
+
*
|
|
4
|
+
* The Svelte peer of `@atlasauth/react`: the same surface, expressed as Svelte
|
|
5
|
+
* stores, context, and `.svelte` components. Install the client with
|
|
6
|
+
* {@link initAtlas} (or `<AtlasProvider>`), read reactive state through the
|
|
7
|
+
* accessors, and gate UI with the components.
|
|
8
|
+
*
|
|
9
|
+
* The SvelteKit server helper (`handleAtlas`, `getServerAuth`) lives at the
|
|
10
|
+
* separate `@atlasauth/svelte/server` entry — it depends on `@atlasauth/backend` and
|
|
11
|
+
* must never be pulled into a browser bundle.
|
|
12
|
+
*/
|
|
13
|
+
export * from './appearance';
|
|
14
|
+
export * from './protect';
|
|
15
|
+
export * from './i18n';
|
|
16
|
+
export * from './types';
|
|
17
|
+
export * from './fapi';
|
|
18
|
+
export { ATLAS_KEY, initAtlas, createAtlasClient, useAtlas, type AuthStatus, type AtlasInitOptions, type AtlasStores, type AtlasClient, } from './context';
|
|
19
|
+
export { getUser, getSession, getAuth, getOrganization, getToken, signIn, signUp, signOut, type UseUser, type UseSession, type UseAuth, type UseOrganization, type FlowController, } from './accessors';
|
|
20
|
+
export { default as AtlasProvider } from './components/AtlasProvider.svelte';
|
|
21
|
+
export { default as SignedIn } from './components/SignedIn.svelte';
|
|
22
|
+
export { default as SignedOut } from './components/SignedOut.svelte';
|
|
23
|
+
export { default as AtlasLoading } from './components/AtlasLoading.svelte';
|
|
24
|
+
export { default as AtlasLoaded } from './components/AtlasLoaded.svelte';
|
|
25
|
+
export { default as Protect } from './components/Protect.svelte';
|
|
26
|
+
export { default as SignIn } from './components/SignIn.svelte';
|
|
27
|
+
export { default as SignUp } from './components/SignUp.svelte';
|
|
28
|
+
export { default as UserButton } from './components/UserButton.svelte';
|
|
29
|
+
export { default as UserProfile } from './components/UserProfile.svelte';
|
|
30
|
+
export { default as OrganizationSwitcher } from './components/OrganizationSwitcher.svelte';
|
|
31
|
+
export { default as OrganizationProfile } from './components/OrganizationProfile.svelte';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@atlasauth/svelte` — the official Svelte / SvelteKit SDK for Atlas.
|
|
3
|
+
*
|
|
4
|
+
* The Svelte peer of `@atlasauth/react`: the same surface, expressed as Svelte
|
|
5
|
+
* stores, context, and `.svelte` components. Install the client with
|
|
6
|
+
* {@link initAtlas} (or `<AtlasProvider>`), read reactive state through the
|
|
7
|
+
* accessors, and gate UI with the components.
|
|
8
|
+
*
|
|
9
|
+
* The SvelteKit server helper (`handleAtlas`, `getServerAuth`) lives at the
|
|
10
|
+
* separate `@atlasauth/svelte/server` entry — it depends on `@atlasauth/backend` and
|
|
11
|
+
* must never be pulled into a browser bundle.
|
|
12
|
+
*/
|
|
13
|
+
// Theming, i18n, authorization, and shared types (identical to @atlasauth/react).
|
|
14
|
+
export * from './appearance';
|
|
15
|
+
export * from './protect';
|
|
16
|
+
export * from './i18n';
|
|
17
|
+
export * from './types';
|
|
18
|
+
export * from './fapi';
|
|
19
|
+
// The client, context install, and the base accessor.
|
|
20
|
+
export { ATLAS_KEY, initAtlas, createAtlasClient, useAtlas, } from './context';
|
|
21
|
+
// Store accessors (peers of the React hooks) and action helpers.
|
|
22
|
+
export { getUser, getSession, getAuth, getOrganization, getToken, signIn, signUp, signOut, } from './accessors';
|
|
23
|
+
// Components.
|
|
24
|
+
export { default as AtlasProvider } from './components/AtlasProvider.svelte';
|
|
25
|
+
export { default as SignedIn } from './components/SignedIn.svelte';
|
|
26
|
+
export { default as SignedOut } from './components/SignedOut.svelte';
|
|
27
|
+
export { default as AtlasLoading } from './components/AtlasLoading.svelte';
|
|
28
|
+
export { default as AtlasLoaded } from './components/AtlasLoaded.svelte';
|
|
29
|
+
export { default as Protect } from './components/Protect.svelte';
|
|
30
|
+
export { default as SignIn } from './components/SignIn.svelte';
|
|
31
|
+
export { default as SignUp } from './components/SignUp.svelte';
|
|
32
|
+
export { default as UserButton } from './components/UserButton.svelte';
|
|
33
|
+
export { default as UserProfile } from './components/UserProfile.svelte';
|
|
34
|
+
export { default as OrganizationSwitcher } from './components/OrganizationSwitcher.svelte';
|
|
35
|
+
export { default as OrganizationProfile } from './components/OrganizationProfile.svelte';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §10.2 `<Protect permission="org:sys_memberships:manage">` — conditional
|
|
3
|
+
* rendering.
|
|
4
|
+
*
|
|
5
|
+
* The rule that makes this safe to ship: this is a RENDERING helper, never an
|
|
6
|
+
* authorization boundary. It decides what a user sees, and seeing is not doing.
|
|
7
|
+
* Anyone can open devtools and flip the condition; the only thing that stops
|
|
8
|
+
* them acting is the server checking the same permission on the request.
|
|
9
|
+
*
|
|
10
|
+
* That is worth stating loudly because the component reads like a guard, and a
|
|
11
|
+
* developer who believes it is one will eventually put a delete button behind
|
|
12
|
+
* it and no check behind the endpoint.
|
|
13
|
+
*
|
|
14
|
+
* The evaluation logic lives in `@atlasauth/authz` — the ONE primitive every SDK
|
|
15
|
+
* (backend, Next.js, React, React Native, JS) shares, so `<Protect>` here and
|
|
16
|
+
* `auth().has()` on the server judge a condition identically.
|
|
17
|
+
*/
|
|
18
|
+
export { type ProtectCondition, type ProtectOutcome, type ProtectReason, evaluate, hasFromClaims, } from '@atlasauth/authz';
|
package/dist/protect.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §10.2 `<Protect permission="org:sys_memberships:manage">` — conditional
|
|
3
|
+
* rendering.
|
|
4
|
+
*
|
|
5
|
+
* The rule that makes this safe to ship: this is a RENDERING helper, never an
|
|
6
|
+
* authorization boundary. It decides what a user sees, and seeing is not doing.
|
|
7
|
+
* Anyone can open devtools and flip the condition; the only thing that stops
|
|
8
|
+
* them acting is the server checking the same permission on the request.
|
|
9
|
+
*
|
|
10
|
+
* That is worth stating loudly because the component reads like a guard, and a
|
|
11
|
+
* developer who believes it is one will eventually put a delete button behind
|
|
12
|
+
* it and no check behind the endpoint.
|
|
13
|
+
*
|
|
14
|
+
* The evaluation logic lives in `@atlasauth/authz` — the ONE primitive every SDK
|
|
15
|
+
* (backend, Next.js, React, React Native, JS) shares, so `<Protect>` here and
|
|
16
|
+
* `auth().has()` on the server judge a condition identically.
|
|
17
|
+
*/
|
|
18
|
+
export { evaluate, hasFromClaims, } from '@atlasauth/authz';
|