@learnorama/mozaik-sdk 0.1.0-staging.12.1 → 0.1.0-staging.13.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.
- package/README.md +114 -3
- package/dist/api/index.js +1 -1
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +451 -0
- package/dist/auth.js.map +1 -0
- package/dist/chunks/client2.d.ts +53 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react/sso.d.ts +15 -0
- package/dist/react/sso.js +48 -0
- package/dist/react/sso.js.map +1 -0
- package/package.json +14 -6
package/README.md
CHANGED
|
@@ -35,9 +35,120 @@ The token getter runs for every request, so refreshing a token does not require
|
|
|
35
35
|
recreating the client. Clients can also supply default headers and a custom fetch
|
|
36
36
|
implementation. Request options accept headers and an AbortSignal.
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
The basic `createClient` transport uses the token getter you provide. For managed
|
|
39
|
+
PKCE and session refresh, use the SSO client below. Neither transport automatically
|
|
40
|
+
replays failed application writes. Backend-owned Keycloak client secrets must
|
|
41
|
+
never be supplied to this SDK.
|
|
42
|
+
|
|
43
|
+
## Google Workspace and partner SSO
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
// auth.ts — one instance for the browser application
|
|
47
|
+
import { createMozaikAuth } from '@learnorama/mozaik-sdk/auth';
|
|
48
|
+
|
|
49
|
+
export const auth = createMozaikAuth({
|
|
50
|
+
baseUrl: 'https://api.stg.mozaik.co',
|
|
51
|
+
redirectUri: 'https://your-app.example/auth/callback',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// A click handler: prepare returns a URL; navigation stays under your control.
|
|
55
|
+
const { url } = await auth.prepareInternalSso({ returnTo: '/requests' });
|
|
56
|
+
window.location.assign(url);
|
|
57
|
+
|
|
58
|
+
// On /auth/callback, using the same configuration after the redirect:
|
|
59
|
+
const { account, returnTo } = await auth.completeSso();
|
|
60
|
+
window.location.replace(returnTo);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Register the application's callback URL in Keycloak and allow its origin in the
|
|
64
|
+
backend's `AUTH_ALLOWED_REDIRECT_ORIGINS`. Replace `your-app.example` with your
|
|
65
|
+
frontend origin. Start login on that origin so the callback can read the same
|
|
66
|
+
tab's transaction storage.
|
|
67
|
+
|
|
68
|
+
| Method | Behavior |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `prepareInternalSso({ returnTo? })` | Selects the existing `google` Workspace staff provider. |
|
|
71
|
+
| `prepareProviderSso(alias, { returnTo? })` | Selects a registered, brokered partner; the backend validates the alias. |
|
|
72
|
+
| `prepareSso({ returnTo? })` | Lets the backend resolve the provider from the configured API hostname. |
|
|
73
|
+
| `completeSso(callbackUrl?)` | Validates and consumes the transaction, exchanges the code through the backend, and returns account, principal and return path. Defaults to the current browser URL. |
|
|
74
|
+
| `refresh()` | Refreshes the session; concurrent callers share one exchange. |
|
|
75
|
+
| `logout()` | Clears local authentication immediately and revokes the refresh token. |
|
|
76
|
+
| `dispose()` | Local teardown for the application owner; does not revoke the server session. |
|
|
77
|
+
|
|
78
|
+
Preparation also accepts `prompt: 'login' | 'none' | 'consent'`. Return paths must
|
|
79
|
+
be local absolute paths such as `/requests`, never an external URL. PKCE uses
|
|
80
|
+
S256; state, callback address, transaction expiry, expected provider and any
|
|
81
|
+
returned issuer are checked. The code exchange stays on our backend so first
|
|
82
|
+
login provisions the local account. Google Workspace membership and permissions
|
|
83
|
+
remain enforced by Keycloak and the backend.
|
|
84
|
+
|
|
85
|
+
Only short-lived PKCE transactions are saved to per-tab `sessionStorage` (ten
|
|
86
|
+
minutes). Access and refresh tokens stay in memory and are absent from the auth
|
|
87
|
+
snapshot. **Reloading the page starts without a local session**: initiate SSO again;
|
|
88
|
+
an existing Keycloak/Workspace session may complete it without another credential
|
|
89
|
+
prompt. This implementation uses full-page redirects, not popup messaging or
|
|
90
|
+
silent iframe authentication.
|
|
91
|
+
|
|
92
|
+
The client refreshes before expiry and on demand, coordinating token rotation.
|
|
93
|
+
Transient refresh failures may use an access token only until its expiry; rejected
|
|
94
|
+
or expired sessions clear local authentication. Remote logout errors reject the
|
|
95
|
+
promise while leaving the application signed out. Tokens are attached through
|
|
96
|
+
`auth.client`; its identity changes on login/logout and stays stable across
|
|
97
|
+
refreshes. Old client instances reject requests after a session change.
|
|
98
|
+
|
|
99
|
+
## React SSO shortcut
|
|
100
|
+
|
|
101
|
+
`MozaikAuthProvider` supplies both authentication and its own TanStack Query
|
|
102
|
+
provider. Put SDK consumers inside it; an additional QueryClientProvider is not
|
|
103
|
+
needed for that subtree. `/react/auth` still contains the generated HTTP hooks;
|
|
104
|
+
the handwritten SSO integration is `/react/sso`.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { useEffect } from 'react';
|
|
108
|
+
import { MozaikAuthProvider, useMozaikAuth } from '@learnorama/mozaik-sdk/react/sso';
|
|
109
|
+
import { useReadMe } from '@learnorama/mozaik-sdk/react/auth';
|
|
110
|
+
import { auth } from './auth';
|
|
111
|
+
|
|
112
|
+
export function Root() {
|
|
113
|
+
return <MozaikAuthProvider auth={auth}><Account /></MozaikAuthProvider>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function Account() {
|
|
117
|
+
const { client, isAuthenticated, isLoading, error, prepareInternalSso, logout } = useMozaikAuth();
|
|
118
|
+
const me = useReadMe({ request: { client }, query: { enabled: isAuthenticated } });
|
|
119
|
+
async function signIn() {
|
|
120
|
+
try {
|
|
121
|
+
const { url } = await prepareInternalSso({ returnTo: '/requests' });
|
|
122
|
+
window.location.assign(url);
|
|
123
|
+
} catch { /* error is exposed by useMozaikAuth */ }
|
|
124
|
+
}
|
|
125
|
+
if (isAuthenticated) return <button onClick={() => { void logout().catch(() => {}); }}>Sign out {me.data?.account.email}</button>;
|
|
126
|
+
return <><button disabled={isLoading} onClick={() => { void signIn(); }}>Sign in with Google Workspace</button>{error && <p role="alert">{error.message}</p>}</>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Render this at /auth/callback, also inside MozaikAuthProvider.
|
|
130
|
+
export function SsoCallback() {
|
|
131
|
+
const { completeSso, error } = useMozaikAuth();
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
void completeSso().then(({ returnTo }) => window.location.replace(returnTo)).catch(() => {});
|
|
134
|
+
}, [completeSso]);
|
|
135
|
+
return <p>{error ? error.message : 'Completing sign-in…'}</p>;
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Concurrent callback completions share one exchange. The callback parameters are
|
|
140
|
+
removed from browser history; repeated default completion calls on that cleaned
|
|
141
|
+
URL are safe during React remounts. Replaying an explicit consumed callback is
|
|
142
|
+
rejected. Login/logout replace the query cache and remount the session subtree,
|
|
143
|
+
preventing previous-account results from appearing. Refresh retains the cache.
|
|
144
|
+
For chat, use the same auth `client` and `enabled: isAuthenticated` with
|
|
145
|
+
`useTutoringChat`; session changes close the previous connection automatically.
|
|
146
|
+
|
|
147
|
+
Create the auth instance outside component renders, once per browser application
|
|
148
|
+
or per server request. The provider does not call `dispose()` on unmount: this
|
|
149
|
+
would invalidate the shared controller during Strict Mode's cleanup/re-setup.
|
|
150
|
+
The application owner may dispose it on permanent teardown. Browser operations
|
|
151
|
+
require Web Crypto and sessionStorage in a secure context.
|
|
41
152
|
|
|
42
153
|
## React
|
|
43
154
|
|
package/dist/api/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { getListMessagesUrl, getMintChatTicketUrl, getSendMessageUrl, listMessages, mintChatTicket, sendMessage } from "./tutoring-chat.js";
|
|
3
|
+
import { authorization, createSessionFromCode, createSessionFromCredentials, deleteSession, getAuthorizationUrl, getCreateSessionFromCodeUrl, getCreateSessionFromCredentialsUrl, getDeleteSessionUrl, getReadMeUrl, getRefreshSessionUrl, readMe, refreshSession } from "./auth.js";
|
|
3
4
|
import { deleteAccount, getDeleteAccountUrl, getListServicesUrl, getReadAccountUrl, getUpdateAccountUrl, listServices, readAccount, updateAccount } from "./account.js";
|
|
4
5
|
import { amendTheme, createTheme, deleteTheme, getAmendThemeUrl, getCreateThemeUrl, getDeleteThemeUrl, getListThemesUrl, getReadThemeUrl, getReorderThemesUrl, listThemes, readTheme, reorderThemes } from "./admin-catalogue.js";
|
|
5
6
|
import { forceCloseLease, getForceCloseLeaseUrl, getListLiveLeasesUrl, listLiveLeases } from "./admin-consumption.js";
|
|
@@ -10,7 +11,6 @@ import { getListGrantsUrl, getReplaceGrantsUrl, listGrants, replaceGrants } from
|
|
|
10
11
|
import { closeSupportSession, createDemoUser, getCloseSupportSessionUrl, getCreateDemoUserUrl, getOpenSupportSessionUrl, openSupportSession } from "./admin-support.js";
|
|
11
12
|
import { getListTenantsUrl, getReadTenantUrl, getUpdateTenantUrl, listTenants, readTenant, updateTenant } from "./admin-tenants.js";
|
|
12
13
|
import { adminCloseRequest, amendGoal, amendLevel, amendPractitioner, amendRequest, amendSubject, createGoal, createLevel, createSubject, getAdminCloseRequestUrl, getAmendGoalUrl, getAmendLevelUrl, getAmendPractitionerUrl, getAmendRequestUrl, getAmendSubjectUrl, getCreateGoalUrl, getCreateLevelUrl, getCreateSubjectUrl, getListAllRequestsUrl, getListExpiringDocumentsUrl, getListFeedbackUrl, getListPractitionerDocumentsUrl, getListPractitionersUrl, getModerateFeedbackUrl, getReadDraftUrl, getReadRequestUrl, getReviewDraftUrl, listAllRequests, listExpiringDocuments, listFeedback, listPractitionerDocuments, listPractitioners, moderateFeedback, readDraft, readRequest, reviewDraft } from "./admin-tutoring.js";
|
|
13
|
-
import { authorization, createSessionFromCode, createSessionFromCredentials, deleteSession, getAuthorizationUrl, getCreateSessionFromCodeUrl, getCreateSessionFromCredentialsUrl, getDeleteSessionUrl, getReadMeUrl, getRefreshSessionUrl, readMe, refreshSession } from "./auth.js";
|
|
14
14
|
import { getLikeContentUrl, getListEditorsUrl, getReadCatalogThemeUrl, getReadCatalogUrl, getReadContentUrl, getReadEditorUrl, getReadLearningPathUrl, getReadPackUrl, getReadRecommendationsUrl, getUnlikeContentUrl, likeContent, listEditors, readCatalog, readCatalogTheme, readContent, readEditor, readLearningPath, readPack, readRecommendations, unlikeContent } from "./biblio-catalogue.js";
|
|
15
15
|
import { getReadHomeFeedUrl, readHomeFeed } from "./biblio-home.js";
|
|
16
16
|
import { addBiblioFavorite, getAddBiblioFavoriteUrl, getListBiblioCategoriesUrl, getListBiblioFavoritesUrl, getListHistoryUrl, getListPacksUrl, getReadPreferencesUrl, getRemoveBiblioFavoriteUrl, getReplaceCategoryPreferencesUrl, listBiblioCategories, listBiblioFavorites, listHistory, listPacks, readPreferences, removeBiblioFavorite, replaceCategoryPreferences } from "./biblio-library.js";
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
import { a as PreparedSso, i as MozaikAuthOptions, n as CompletedSso, o as SsoOptions, r as MozaikAuth, s as createMozaikAuth, t as AuthSnapshot } from "./chunks/client2.js";
|
|
3
|
+
//#region src/auth/transactions.d.ts
|
|
4
|
+
export declare class AuthError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
constructor(code: string, message: string);
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
export { type AuthSnapshot, type CompletedSso, type MozaikAuth, type MozaikAuthOptions, type PreparedSso, type SsoOptions, createMozaikAuth };
|
|
10
|
+
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
|
|
2
|
+
import { n as createClient, t as ApiError } from "./chunks/client.js";
|
|
3
|
+
import { authorization, createSessionFromCode, deleteSession, refreshSession } from "./api/auth.js";
|
|
4
|
+
//#region src/auth/transactions.ts
|
|
5
|
+
var AuthError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(code, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.name = "AuthError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const transactionLifetime = 6e5;
|
|
14
|
+
const oauthParameters = [
|
|
15
|
+
"code",
|
|
16
|
+
"state",
|
|
17
|
+
"iss",
|
|
18
|
+
"session_state",
|
|
19
|
+
"error",
|
|
20
|
+
"error_description",
|
|
21
|
+
"error_uri"
|
|
22
|
+
];
|
|
23
|
+
function callbackAddress(value) {
|
|
24
|
+
const url = new URL(value);
|
|
25
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new TypeError("redirectUri must be an absolute HTTP(S) callback URL without credentials, query or fragment");
|
|
26
|
+
return url;
|
|
27
|
+
}
|
|
28
|
+
function localReturnTo(value, origin) {
|
|
29
|
+
if (!value.startsWith("/") || value.startsWith("//") || /[\\\u0000-\u0020\u007f]/.test(value)) throw new AuthError("invalid_return_to", "returnTo must be a local absolute path");
|
|
30
|
+
const url = new URL(value, origin);
|
|
31
|
+
if (url.origin !== origin) throw new AuthError("invalid_return_to", "returnTo must stay on this application");
|
|
32
|
+
return url.pathname + url.search + url.hash;
|
|
33
|
+
}
|
|
34
|
+
function randomSecret() {
|
|
35
|
+
return base64url(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(32)));
|
|
36
|
+
}
|
|
37
|
+
function base64url(bytes) {
|
|
38
|
+
return btoa(String.fromCharCode(...bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
39
|
+
}
|
|
40
|
+
async function challenge(verifier) {
|
|
41
|
+
return base64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
|
|
42
|
+
}
|
|
43
|
+
function transactionStore(baseUrl, redirect, supplied) {
|
|
44
|
+
const prefix = `mozaik:sso:${encodeURIComponent(baseUrl)}:${encodeURIComponent(redirect.href)}:`;
|
|
45
|
+
const storage = () => {
|
|
46
|
+
if (supplied) return supplied;
|
|
47
|
+
if (typeof window === "undefined") throw new AuthError("browser_required", "SSO transactions require browser sessionStorage");
|
|
48
|
+
if (window.location.origin !== redirect.origin) throw new AuthError("invalid_origin", "Start SSO on the callback application origin");
|
|
49
|
+
return window.sessionStorage;
|
|
50
|
+
};
|
|
51
|
+
const keys = (store) => Array.from({ length: store.length }, (_, index) => store.key(index)).filter((key) => key !== null && key.startsWith(prefix));
|
|
52
|
+
return {
|
|
53
|
+
save(state, transaction) {
|
|
54
|
+
const store = storage();
|
|
55
|
+
for (const key of keys(store)) try {
|
|
56
|
+
const old = JSON.parse(store.getItem(key));
|
|
57
|
+
if (!Number.isFinite(old.createdAt) || Date.now() - old.createdAt >= 6e5) store.removeItem(key);
|
|
58
|
+
} catch {
|
|
59
|
+
store.removeItem(key);
|
|
60
|
+
}
|
|
61
|
+
store.setItem(prefix + state, JSON.stringify(transaction));
|
|
62
|
+
},
|
|
63
|
+
consume(state) {
|
|
64
|
+
if (!/^[A-Za-z0-9_-]{43}$/.test(state)) throw new AuthError("invalid_state", "Invalid SSO state");
|
|
65
|
+
const store = storage();
|
|
66
|
+
const saved = store.getItem(prefix + state);
|
|
67
|
+
if (!saved) throw new AuthError("invalid_state", "SSO transaction is missing or already consumed");
|
|
68
|
+
store.removeItem(prefix + state);
|
|
69
|
+
let value;
|
|
70
|
+
try {
|
|
71
|
+
value = JSON.parse(saved);
|
|
72
|
+
} catch {
|
|
73
|
+
throw new AuthError("invalid_state", "Invalid SSO transaction");
|
|
74
|
+
}
|
|
75
|
+
if (!value || !/^[A-Za-z0-9_-]{43}$/.test(value.verifier) || typeof value.issuer !== "string" || typeof value.returnTo !== "string" || !(value.provider === null || typeof value.provider === "string")) throw new AuthError("invalid_state", "Invalid SSO transaction");
|
|
76
|
+
if (!Number.isFinite(value.createdAt) || value.createdAt > Date.now() || Date.now() - value.createdAt >= 6e5) throw new AuthError("expired_transaction", "SSO transaction expired; start sign-in again");
|
|
77
|
+
value.returnTo = localReturnTo(value.returnTo, redirect.origin);
|
|
78
|
+
return value;
|
|
79
|
+
},
|
|
80
|
+
clear() {
|
|
81
|
+
if (!supplied && typeof window === "undefined") return;
|
|
82
|
+
const store = storage();
|
|
83
|
+
for (const key of keys(store)) store.removeItem(key);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function parseCallback(value, redirect) {
|
|
88
|
+
const url = new URL(value);
|
|
89
|
+
if (url.origin !== redirect.origin || url.pathname !== redirect.pathname || url.username || url.password || url.hash) throw new AuthError("invalid_callback", "SSO callback does not match redirectUri");
|
|
90
|
+
cleanCallback(url);
|
|
91
|
+
if (oauthParameters.some((key) => url.searchParams.getAll(key).length > 1)) throw new AuthError("invalid_callback", "SSO callback contains duplicate parameters");
|
|
92
|
+
return url;
|
|
93
|
+
}
|
|
94
|
+
function cleanCallback(url) {
|
|
95
|
+
const clean = new URL(url);
|
|
96
|
+
for (const key of oauthParameters) clean.searchParams.delete(key);
|
|
97
|
+
if (typeof window !== "undefined" && window.location.href === url.href) window.history.replaceState(window.history.state, "", clean.href);
|
|
98
|
+
return clean.href;
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/auth/client.ts
|
|
102
|
+
const toError = (value) => value instanceof Error ? value : new AuthError("request_failed", "Authentication request failed");
|
|
103
|
+
const changed = () => new AuthError("session_changed", "Authentication session changed");
|
|
104
|
+
/** Browser SSO through Mozaik's account-provisioning backend, with no client secret. */
|
|
105
|
+
function createMozaikAuth(options) {
|
|
106
|
+
const redirect = callbackAddress(options.redirectUri);
|
|
107
|
+
const publicClient = createClient({
|
|
108
|
+
baseUrl: options.baseUrl,
|
|
109
|
+
fetch: options.fetch
|
|
110
|
+
});
|
|
111
|
+
const transactions = transactionStore(publicClient.baseUrl, redirect, options.transactionStorage);
|
|
112
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
113
|
+
const pending = /* @__PURE__ */ new Set();
|
|
114
|
+
const callbacks = /* @__PURE__ */ new Map();
|
|
115
|
+
let completed;
|
|
116
|
+
let disposed = false, epoch = 0;
|
|
117
|
+
let session;
|
|
118
|
+
let error = null;
|
|
119
|
+
let abort = new AbortController();
|
|
120
|
+
let timer;
|
|
121
|
+
let flight;
|
|
122
|
+
let client = makeClient();
|
|
123
|
+
let snapshot = makeSnapshot();
|
|
124
|
+
function assertUsable() {
|
|
125
|
+
if (disposed) throw new AuthError("disposed", "Authentication client was disposed");
|
|
126
|
+
}
|
|
127
|
+
function assertEpoch(expected) {
|
|
128
|
+
assertUsable();
|
|
129
|
+
if (epoch !== expected) throw changed();
|
|
130
|
+
}
|
|
131
|
+
function makeSnapshot() {
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
status: pending.size ? "authenticating" : flight?.epoch === epoch ? "refreshing" : session ? "authenticated" : "anonymous",
|
|
134
|
+
isAuthenticated: Boolean(session),
|
|
135
|
+
isLoading: pending.size > 0 || flight?.epoch === epoch,
|
|
136
|
+
account: session?.data.account ?? null,
|
|
137
|
+
principal: session?.data.principal ?? null,
|
|
138
|
+
error,
|
|
139
|
+
client
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function publish() {
|
|
143
|
+
snapshot = makeSnapshot();
|
|
144
|
+
for (const listener of listeners) listener();
|
|
145
|
+
}
|
|
146
|
+
function clearSession() {
|
|
147
|
+
epoch++;
|
|
148
|
+
abort.abort();
|
|
149
|
+
abort = new AbortController();
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
timer = void 0;
|
|
152
|
+
session = void 0;
|
|
153
|
+
pending.clear();
|
|
154
|
+
flight = void 0;
|
|
155
|
+
completed = void 0;
|
|
156
|
+
client = makeClient();
|
|
157
|
+
}
|
|
158
|
+
function makeClient() {
|
|
159
|
+
const boundEpoch = epoch, boundSignal = abort.signal;
|
|
160
|
+
return createClient({
|
|
161
|
+
baseUrl: publicClient.baseUrl,
|
|
162
|
+
getAccessToken: async () => {
|
|
163
|
+
assertEpoch(boundEpoch);
|
|
164
|
+
if (session && Date.now() >= session.refreshAt) try {
|
|
165
|
+
await refresh();
|
|
166
|
+
} catch (failure) {
|
|
167
|
+
if (!session || Date.now() >= session.expiresAt) throw failure;
|
|
168
|
+
}
|
|
169
|
+
assertEpoch(boundEpoch);
|
|
170
|
+
return session?.data.access_token;
|
|
171
|
+
},
|
|
172
|
+
fetch: async (url, init) => {
|
|
173
|
+
assertEpoch(boundEpoch);
|
|
174
|
+
const signal = init?.signal ? AbortSignal.any([init.signal, boundSignal]) : boundSignal;
|
|
175
|
+
signal.throwIfAborted();
|
|
176
|
+
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
177
|
+
...init,
|
|
178
|
+
signal
|
|
179
|
+
});
|
|
180
|
+
assertEpoch(boundEpoch);
|
|
181
|
+
return response;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function validateSession(data) {
|
|
186
|
+
if (!data || typeof data.access_token !== "string" || !data.access_token || !Number.isFinite(data.expires_in) || data.expires_in <= 0 || !data.account || !data.principal || typeof data.principal.subject !== "string" || data.refresh_token != null && (typeof data.refresh_token !== "string" || !data.refresh_token)) throw new AuthError("invalid_response", "Invalid authentication session response");
|
|
187
|
+
}
|
|
188
|
+
function schedule(delay) {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
timer = setTimeout(() => {
|
|
191
|
+
refresh().catch(() => {});
|
|
192
|
+
}, Math.min(2147483647, Math.max(0, delay)));
|
|
193
|
+
timer.unref?.();
|
|
194
|
+
}
|
|
195
|
+
function install(data) {
|
|
196
|
+
validateSession(data);
|
|
197
|
+
const now = Date.now(), ttl = data.expires_in * 1e3;
|
|
198
|
+
session = {
|
|
199
|
+
data,
|
|
200
|
+
expiresAt: now + ttl,
|
|
201
|
+
refreshAt: now + ttl - (data.refresh_token ? Math.min(3e4, ttl / 2) : 0),
|
|
202
|
+
refreshExpiresAt: data.refresh_expires_in && data.refresh_expires_in > 0 ? now + data.refresh_expires_in * 1e3 : void 0
|
|
203
|
+
};
|
|
204
|
+
error = null;
|
|
205
|
+
schedule(session.refreshAt - now);
|
|
206
|
+
}
|
|
207
|
+
async function revoke(token) {
|
|
208
|
+
if (token) await deleteSession({ refresh_token: token }, {
|
|
209
|
+
client: publicClient,
|
|
210
|
+
signal: AbortSignal.timeout(2e4)
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function refresh() {
|
|
214
|
+
assertUsable();
|
|
215
|
+
if (flight?.epoch === epoch) return flight.applied;
|
|
216
|
+
const current = session, expected = epoch;
|
|
217
|
+
if (!current) return Promise.reject(new AuthError("session_expired", "Sign in before refreshing a session"));
|
|
218
|
+
if (!current.data.refresh_token || current.refreshExpiresAt && Date.now() >= current.refreshExpiresAt) {
|
|
219
|
+
const expired = new AuthError("session_expired", "Session expired; sign in again");
|
|
220
|
+
clearSession();
|
|
221
|
+
error = expired;
|
|
222
|
+
publish();
|
|
223
|
+
return Promise.reject(expired);
|
|
224
|
+
}
|
|
225
|
+
const requestAbort = new AbortController();
|
|
226
|
+
const response = refreshSession({ refresh_token: current.data.refresh_token }, {
|
|
227
|
+
client: publicClient,
|
|
228
|
+
signal: AbortSignal.any([requestAbort.signal, AbortSignal.timeout(2e4)])
|
|
229
|
+
});
|
|
230
|
+
const running = {
|
|
231
|
+
epoch: expected,
|
|
232
|
+
response,
|
|
233
|
+
applied: Promise.resolve(),
|
|
234
|
+
abort: requestAbort
|
|
235
|
+
};
|
|
236
|
+
flight = running;
|
|
237
|
+
running.applied = response.then((data) => {
|
|
238
|
+
assertEpoch(expected);
|
|
239
|
+
validateSession(data);
|
|
240
|
+
if (data.principal.subject !== current.data.principal.subject || data.account.id !== current.data.account.id) throw new AuthError("invalid_response", "Refresh changed the authenticated account");
|
|
241
|
+
install(data);
|
|
242
|
+
}).catch((failure) => {
|
|
243
|
+
if (!disposed && expected === epoch) {
|
|
244
|
+
error = toError(failure);
|
|
245
|
+
if (failure instanceof ApiError && [401, 403].includes(failure.status) || failure instanceof AuthError || Date.now() >= current.expiresAt) clearSession();
|
|
246
|
+
else {
|
|
247
|
+
current.refreshAt = Math.min(current.expiresAt, Date.now() + 5e3);
|
|
248
|
+
schedule(current.refreshAt - Date.now());
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
throw failure;
|
|
252
|
+
}).finally(() => {
|
|
253
|
+
if (flight === running) flight = void 0;
|
|
254
|
+
if (!disposed && expected === epoch) publish();
|
|
255
|
+
else if (!disposed && !session) publish();
|
|
256
|
+
});
|
|
257
|
+
publish();
|
|
258
|
+
return running.applied;
|
|
259
|
+
}
|
|
260
|
+
async function prepare(provider, settings = {}) {
|
|
261
|
+
assertUsable();
|
|
262
|
+
if (provider !== void 0 && (!provider.trim() || provider.length > 64)) throw new TypeError("A registered provider alias is required");
|
|
263
|
+
const returnTo = localReturnTo(settings.returnTo ?? "/", redirect.origin);
|
|
264
|
+
const expected = epoch, operation = Symbol();
|
|
265
|
+
pending.add(operation);
|
|
266
|
+
error = null;
|
|
267
|
+
publish();
|
|
268
|
+
try {
|
|
269
|
+
const state = randomSecret(), verifier = randomSecret(), codeChallenge = await challenge(verifier);
|
|
270
|
+
assertEpoch(expected);
|
|
271
|
+
const result = await authorization({
|
|
272
|
+
redirect_uri: redirect.href,
|
|
273
|
+
code_challenge: codeChallenge,
|
|
274
|
+
state,
|
|
275
|
+
provider,
|
|
276
|
+
prompt: settings.prompt
|
|
277
|
+
}, {
|
|
278
|
+
client: publicClient,
|
|
279
|
+
signal: AbortSignal.any([abort.signal, AbortSignal.timeout(2e4)])
|
|
280
|
+
});
|
|
281
|
+
assertEpoch(expected);
|
|
282
|
+
const url = new URL(result.authorizationUrl);
|
|
283
|
+
if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.hash || !url.pathname.endsWith("/protocol/openid-connect/auth") || [
|
|
284
|
+
"state",
|
|
285
|
+
"code_challenge",
|
|
286
|
+
"code_challenge_method",
|
|
287
|
+
"redirect_uri",
|
|
288
|
+
"response_type"
|
|
289
|
+
].some((key) => url.searchParams.getAll(key).length !== 1) || url.searchParams.get("state") !== state || url.searchParams.get("code_challenge") !== codeChallenge || url.searchParams.get("code_challenge_method") !== "S256" || url.searchParams.get("redirect_uri") !== redirect.href || url.searchParams.get("response_type") !== "code" || provider === "google" && result.provider !== "google") throw new AuthError("invalid_response", "Invalid SSO authorization response");
|
|
290
|
+
const createdAt = Date.now();
|
|
291
|
+
transactions.save(state, {
|
|
292
|
+
verifier,
|
|
293
|
+
createdAt,
|
|
294
|
+
returnTo,
|
|
295
|
+
provider: result.provider ?? null,
|
|
296
|
+
issuer: url.origin + url.pathname.slice(0, -29)
|
|
297
|
+
});
|
|
298
|
+
return {
|
|
299
|
+
url: url.href,
|
|
300
|
+
provider: result.provider ?? null,
|
|
301
|
+
expiresAt: createdAt + transactionLifetime
|
|
302
|
+
};
|
|
303
|
+
} catch (failure) {
|
|
304
|
+
if (expected === epoch && !disposed) error = toError(failure);
|
|
305
|
+
throw failure;
|
|
306
|
+
} finally {
|
|
307
|
+
pending.delete(operation);
|
|
308
|
+
if (expected === epoch && !disposed) publish();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function completeSso(callbackUrl) {
|
|
312
|
+
try {
|
|
313
|
+
assertUsable();
|
|
314
|
+
const url = parseCallback(callbackUrl ?? (typeof window === "undefined" ? "" : window.location.href), redirect);
|
|
315
|
+
const state = url.searchParams.get("state") ?? "";
|
|
316
|
+
if (callbackUrl === void 0 && !state) {
|
|
317
|
+
const pendingCallback = [...callbacks.values()].find((item) => item.cleanUrl === url.href);
|
|
318
|
+
if (pendingCallback) return pendingCallback.promise;
|
|
319
|
+
if (completed?.epoch === epoch && completed.url === url.href) return Promise.resolve(completed.result);
|
|
320
|
+
}
|
|
321
|
+
const existing = callbacks.get(state);
|
|
322
|
+
if (existing) {
|
|
323
|
+
if (existing.url !== url.href) throw new AuthError("invalid_callback", "A different callback is already using this state");
|
|
324
|
+
return existing.promise;
|
|
325
|
+
}
|
|
326
|
+
const cleanUrl = cleanCallback(url);
|
|
327
|
+
const transaction = transactions.consume(state);
|
|
328
|
+
if (url.searchParams.has("iss") && url.searchParams.get("iss") !== transaction.issuer) throw new AuthError("invalid_issuer", "Unexpected SSO callback issuer");
|
|
329
|
+
if (url.searchParams.has("error")) throw new AuthError("provider_error", "Sign-in was refused or cancelled by the identity provider");
|
|
330
|
+
const code = url.searchParams.get("code");
|
|
331
|
+
if (!code || code.length > 4096) throw new AuthError("invalid_callback", "SSO callback is missing a valid authorization code");
|
|
332
|
+
if (session || flight || callbacks.size || pending.size) clearSession();
|
|
333
|
+
const expected = epoch, operation = Symbol();
|
|
334
|
+
pending.add(operation);
|
|
335
|
+
error = null;
|
|
336
|
+
publish();
|
|
337
|
+
const completion = (async () => {
|
|
338
|
+
try {
|
|
339
|
+
const data = await createSessionFromCode({
|
|
340
|
+
code,
|
|
341
|
+
codeVerifier: transaction.verifier,
|
|
342
|
+
redirectUri: redirect.href,
|
|
343
|
+
state
|
|
344
|
+
}, {
|
|
345
|
+
client: publicClient,
|
|
346
|
+
signal: AbortSignal.timeout(2e4)
|
|
347
|
+
});
|
|
348
|
+
if (disposed || expected !== epoch) {
|
|
349
|
+
await revoke(data.refresh_token).catch(() => {});
|
|
350
|
+
throw changed();
|
|
351
|
+
}
|
|
352
|
+
validateSession(data);
|
|
353
|
+
if (transaction.provider && data.principal.provider !== transaction.provider) {
|
|
354
|
+
await revoke(data.refresh_token).catch(() => {});
|
|
355
|
+
throw new AuthError("provider_mismatch", "Sign-in completed with a different identity provider");
|
|
356
|
+
}
|
|
357
|
+
clearSession();
|
|
358
|
+
install(data);
|
|
359
|
+
const result = {
|
|
360
|
+
account: data.account,
|
|
361
|
+
principal: data.principal,
|
|
362
|
+
returnTo: transaction.returnTo
|
|
363
|
+
};
|
|
364
|
+
completed = {
|
|
365
|
+
epoch,
|
|
366
|
+
url: cleanUrl,
|
|
367
|
+
result
|
|
368
|
+
};
|
|
369
|
+
publish();
|
|
370
|
+
return result;
|
|
371
|
+
} catch (failure) {
|
|
372
|
+
if (!disposed && expected === epoch) {
|
|
373
|
+
error = toError(failure);
|
|
374
|
+
publish();
|
|
375
|
+
}
|
|
376
|
+
throw failure;
|
|
377
|
+
} finally {
|
|
378
|
+
callbacks.delete(state);
|
|
379
|
+
pending.delete(operation);
|
|
380
|
+
if (!disposed) publish();
|
|
381
|
+
}
|
|
382
|
+
})();
|
|
383
|
+
callbacks.set(state, {
|
|
384
|
+
url: url.href,
|
|
385
|
+
cleanUrl,
|
|
386
|
+
promise: completion
|
|
387
|
+
});
|
|
388
|
+
return completion;
|
|
389
|
+
} catch (failure) {
|
|
390
|
+
if (!disposed) {
|
|
391
|
+
error = toError(failure);
|
|
392
|
+
publish();
|
|
393
|
+
}
|
|
394
|
+
return Promise.reject(failure);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
get client() {
|
|
399
|
+
return client;
|
|
400
|
+
},
|
|
401
|
+
getSnapshot: () => snapshot,
|
|
402
|
+
subscribe(listener) {
|
|
403
|
+
listeners.add(listener);
|
|
404
|
+
return () => {
|
|
405
|
+
listeners.delete(listener);
|
|
406
|
+
};
|
|
407
|
+
},
|
|
408
|
+
prepareInternalSso: (settings) => prepare("google", settings),
|
|
409
|
+
prepareProviderSso: (alias, settings) => prepare(alias, settings),
|
|
410
|
+
prepareSso: (settings) => prepare(void 0, settings),
|
|
411
|
+
completeSso,
|
|
412
|
+
refresh,
|
|
413
|
+
async logout() {
|
|
414
|
+
assertUsable();
|
|
415
|
+
const old = session, rotating = flight;
|
|
416
|
+
clearSession();
|
|
417
|
+
error = null;
|
|
418
|
+
publish();
|
|
419
|
+
const expected = epoch;
|
|
420
|
+
let cleanupError;
|
|
421
|
+
try {
|
|
422
|
+
transactions.clear();
|
|
423
|
+
} catch (failure) {
|
|
424
|
+
cleanupError = failure;
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
await revoke((await rotating?.response.catch(() => void 0))?.refresh_token ?? old?.data.refresh_token);
|
|
428
|
+
if (cleanupError) throw cleanupError;
|
|
429
|
+
} catch (failure) {
|
|
430
|
+
if (!disposed && expected === epoch) {
|
|
431
|
+
error = toError(failure);
|
|
432
|
+
publish();
|
|
433
|
+
}
|
|
434
|
+
throw failure;
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
dispose() {
|
|
438
|
+
if (disposed) return;
|
|
439
|
+
flight?.abort.abort();
|
|
440
|
+
clearSession();
|
|
441
|
+
disposed = true;
|
|
442
|
+
error = null;
|
|
443
|
+
publish();
|
|
444
|
+
listeners.clear();
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
//#endregion
|
|
449
|
+
export { AuthError, createMozaikAuth };
|
|
450
|
+
|
|
451
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","names":[],"sources":["../src/auth/transactions.ts","../src/auth/client.ts"],"sourcesContent":["// Handwritten. This directory is never an Orval output/cleanup target.\nexport interface Transaction {\n verifier: string;\n createdAt: number;\n returnTo: string;\n issuer: string;\n provider: string | null;\n}\n\nexport class AuthError extends Error {\n constructor(public readonly code: string, message: string) {\n super(message);\n this.name = 'AuthError';\n }\n}\n\nexport const transactionLifetime = 10 * 60_000;\nconst oauthParameters = ['code', 'state', 'iss', 'session_state', 'error', 'error_description', 'error_uri'];\n\nexport function callbackAddress(value: string): URL {\n const url = new URL(value);\n if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) {\n throw new TypeError('redirectUri must be an absolute HTTP(S) callback URL without credentials, query or fragment');\n }\n return url;\n}\n\nexport function localReturnTo(value: string, origin: string): string {\n // Reject protocol-relative URLs and browser-normalized slash/control tricks.\n if (!value.startsWith('/') || value.startsWith('//') || /[\\\\\\u0000-\\u0020\\u007f]/.test(value)) {\n throw new AuthError('invalid_return_to', 'returnTo must be a local absolute path');\n }\n const url = new URL(value, origin);\n if (url.origin !== origin) throw new AuthError('invalid_return_to', 'returnTo must stay on this application');\n return url.pathname + url.search + url.hash;\n}\n\nexport function randomSecret(): string {\n return base64url(crypto.getRandomValues(new Uint8Array(32)));\n}\nfunction base64url(bytes: Uint8Array): string {\n return btoa(String.fromCharCode(...bytes)).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');\n}\nexport async function challenge(verifier: string): Promise<string> {\n return base64url(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier))));\n}\n\nexport function transactionStore(baseUrl: string, redirect: URL, supplied?: Storage) {\n const prefix = `mozaik:sso:${encodeURIComponent(baseUrl)}:${encodeURIComponent(redirect.href)}:`;\n const storage = () => {\n if (supplied) return supplied;\n if (typeof window === 'undefined') throw new AuthError('browser_required', 'SSO transactions require browser sessionStorage');\n if (window.location.origin !== redirect.origin) throw new AuthError('invalid_origin', 'Start SSO on the callback application origin');\n return window.sessionStorage;\n };\n const keys = (store: Storage) => Array.from({ length: store.length }, (_, index) => store.key(index))\n .filter((key): key is string => key !== null && key.startsWith(prefix));\n return {\n save(state: string, transaction: Transaction) {\n const store = storage();\n for (const key of keys(store)) {\n try {\n const old = JSON.parse(store.getItem(key)!);\n if (!Number.isFinite(old.createdAt) || Date.now() - old.createdAt >= transactionLifetime) store.removeItem(key);\n } catch { store.removeItem(key); }\n }\n store.setItem(prefix + state, JSON.stringify(transaction));\n },\n consume(state: string): Transaction {\n if (!/^[A-Za-z0-9_-]{43}$/.test(state)) throw new AuthError('invalid_state', 'Invalid SSO state');\n const store = storage();\n const saved = store.getItem(prefix + state);\n if (!saved) throw new AuthError('invalid_state', 'SSO transaction is missing or already consumed');\n store.removeItem(prefix + state);\n let value: Transaction;\n try { value = JSON.parse(saved); }\n catch { throw new AuthError('invalid_state', 'Invalid SSO transaction'); }\n if (!value || !/^[A-Za-z0-9_-]{43}$/.test(value.verifier) || typeof value.issuer !== 'string' ||\n typeof value.returnTo !== 'string' || !(value.provider === null || typeof value.provider === 'string')) {\n throw new AuthError('invalid_state', 'Invalid SSO transaction');\n }\n if (!Number.isFinite(value.createdAt) || value.createdAt > Date.now() || Date.now() - value.createdAt >= transactionLifetime) {\n throw new AuthError('expired_transaction', 'SSO transaction expired; start sign-in again');\n }\n value.returnTo = localReturnTo(value.returnTo, redirect.origin);\n return value;\n },\n clear() {\n // Factory creation/disposal on the server does not access browser globals.\n if (!supplied && typeof window === 'undefined') return;\n const store = storage();\n for (const key of keys(store)) store.removeItem(key);\n },\n };\n}\n\nexport function parseCallback(value: string, redirect: URL): URL {\n const url = new URL(value);\n if (url.origin !== redirect.origin || url.pathname !== redirect.pathname || url.username || url.password || url.hash) {\n throw new AuthError('invalid_callback', 'SSO callback does not match redirectUri');\n }\n cleanCallback(url);\n if (oauthParameters.some(key => url.searchParams.getAll(key).length > 1)) {\n throw new AuthError('invalid_callback', 'SSO callback contains duplicate parameters');\n }\n return url;\n}\n\nexport function cleanCallback(url: URL): string {\n const clean = new URL(url);\n for (const key of oauthParameters) clean.searchParams.delete(key);\n if (typeof window !== 'undefined' && window.location.href === url.href) {\n window.history.replaceState(window.history.state, '', clean.href);\n }\n return clean.href;\n}\n","import { ApiError, createClient, type Client, type ClientOptions } from '../client';\nimport { authorization, createSessionFromCode, deleteSession, refreshSession } from '../api/auth/auth';\nimport type { AccountRead, PrincipalRead, SessionRead } from '../models';\nimport { AuthError, callbackAddress, challenge, cleanCallback, localReturnTo, parseCallback, randomSecret, transactionLifetime, transactionStore } from './transactions';\n\nexport interface MozaikAuthOptions {\n baseUrl: string;\n redirectUri: string;\n /** Only PKCE transactions are persisted. Access/refresh tokens stay in memory. */\n transactionStorage?: Storage;\n fetch?: ClientOptions['fetch'];\n}\nexport interface SsoOptions {\n returnTo?: string;\n prompt?: 'login' | 'none' | 'consent';\n}\nexport interface PreparedSso { url: string; provider: string | null; expiresAt: number }\nexport interface CompletedSso { account: AccountRead; principal: PrincipalRead; returnTo: string }\nexport interface AuthSnapshot {\n status: 'anonymous' | 'authenticating' | 'authenticated' | 'refreshing';\n isAuthenticated: boolean;\n isLoading: boolean;\n account: AccountRead | null;\n principal: PrincipalRead | null;\n error: Error | null;\n /** Stable across refreshes; replaced on login/logout to isolate account caches. */\n client: Client;\n}\nexport interface MozaikAuth {\n readonly client: Client;\n getSnapshot(): AuthSnapshot;\n subscribe(listener: () => void): () => void;\n prepareInternalSso(options?: SsoOptions): Promise<PreparedSso>;\n prepareProviderSso(alias: string, options?: SsoOptions): Promise<PreparedSso>;\n prepareSso(options?: SsoOptions): Promise<PreparedSso>;\n completeSso(callbackUrl?: string): Promise<CompletedSso>;\n refresh(): Promise<void>;\n logout(): Promise<void>;\n /** Local teardown only. Use logout() to revoke the server session. */\n dispose(): void;\n}\n\ninterface Session { data: SessionRead; expiresAt: number; refreshAt: number; refreshExpiresAt?: number }\nconst toError = (value: unknown) => value instanceof Error ? value : new AuthError('request_failed', 'Authentication request failed');\nconst changed = () => new AuthError('session_changed', 'Authentication session changed');\n\n/** Browser SSO through Mozaik's account-provisioning backend, with no client secret. */\nexport function createMozaikAuth(options: MozaikAuthOptions): MozaikAuth {\n const redirect = callbackAddress(options.redirectUri);\n const publicClient = createClient({ baseUrl: options.baseUrl, fetch: options.fetch });\n const transactions = transactionStore(publicClient.baseUrl, redirect, options.transactionStorage);\n const listeners = new Set<() => void>();\n const pending = new Set<symbol>();\n const callbacks = new Map<string, { url: string; cleanUrl: string; promise: Promise<CompletedSso> }>();\n let completed: { epoch: number; url: string; result: CompletedSso } | undefined;\n let disposed = false, epoch = 0;\n let session: Session | undefined;\n let error: Error | null = null;\n let abort = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n let flight: { epoch: number; response: Promise<SessionRead>; applied: Promise<void>; abort: AbortController } | undefined;\n let client = makeClient();\n let snapshot = makeSnapshot();\n\n function assertUsable() { if (disposed) throw new AuthError('disposed', 'Authentication client was disposed'); }\n function assertEpoch(expected: number) { assertUsable(); if (epoch !== expected) throw changed(); }\n function makeSnapshot(): AuthSnapshot {\n return Object.freeze({\n status: pending.size ? 'authenticating' : flight?.epoch === epoch ? 'refreshing' : session ? 'authenticated' : 'anonymous',\n isAuthenticated: Boolean(session), isLoading: pending.size > 0 || flight?.epoch === epoch,\n account: session?.data.account ?? null, principal: session?.data.principal ?? null, error, client,\n });\n }\n function publish() { snapshot = makeSnapshot(); for (const listener of listeners) listener(); }\n function clearSession() {\n epoch++;\n abort.abort(); abort = new AbortController();\n clearTimeout(timer); timer = undefined;\n session = undefined; pending.clear(); flight = undefined; completed = undefined;\n client = makeClient();\n }\n function makeClient(): Client {\n const boundEpoch = epoch, boundSignal = abort.signal;\n return createClient({\n baseUrl: publicClient.baseUrl,\n getAccessToken: async () => {\n assertEpoch(boundEpoch);\n if (session && Date.now() >= session.refreshAt) {\n try { await refresh(); }\n catch (failure) {\n // A transient early-refresh failure can still use a valid token.\n if (!session || Date.now() >= session.expiresAt) throw failure;\n }\n }\n assertEpoch(boundEpoch);\n return session?.data.access_token;\n },\n fetch: async (url, init) => {\n assertEpoch(boundEpoch);\n const signal = init?.signal ? AbortSignal.any([init.signal, boundSignal]) : boundSignal;\n signal.throwIfAborted();\n const response = await (options.fetch ?? globalThis.fetch)(url, { ...init, signal });\n assertEpoch(boundEpoch);\n return response;\n },\n });\n }\n function validateSession(data: SessionRead): void {\n if (!data || typeof data.access_token !== 'string' || !data.access_token ||\n !Number.isFinite(data.expires_in) || data.expires_in <= 0 ||\n !data.account || !data.principal || typeof data.principal.subject !== 'string' ||\n (data.refresh_token != null && (typeof data.refresh_token !== 'string' || !data.refresh_token))) {\n throw new AuthError('invalid_response', 'Invalid authentication session response');\n }\n }\n function schedule(delay: number) {\n clearTimeout(timer);\n timer = setTimeout(() => { void refresh().catch(() => {}); }, Math.min(2_147_483_647, Math.max(0, delay)));\n // Native/server consumers must be able to terminate without an auth timer keeping Node alive.\n (timer as unknown as { unref?: () => void }).unref?.();\n }\n function install(data: SessionRead) {\n validateSession(data);\n const now = Date.now(), ttl = data.expires_in * 1_000;\n session = {\n data, expiresAt: now + ttl,\n refreshAt: now + ttl - (data.refresh_token ? Math.min(30_000, ttl / 2) : 0),\n refreshExpiresAt: data.refresh_expires_in && data.refresh_expires_in > 0 ? now + data.refresh_expires_in * 1_000 : undefined,\n };\n error = null;\n schedule(session.refreshAt - now);\n }\n async function revoke(token?: string | null) {\n if (token) await deleteSession({ refresh_token: token }, { client: publicClient, signal: AbortSignal.timeout(20_000) });\n }\n\n function refresh(): Promise<void> {\n assertUsable();\n if (flight?.epoch === epoch) return flight.applied;\n const current = session, expected = epoch;\n if (!current) return Promise.reject(new AuthError('session_expired', 'Sign in before refreshing a session'));\n if (!current.data.refresh_token || (current.refreshExpiresAt && Date.now() >= current.refreshExpiresAt)) {\n const expired = new AuthError('session_expired', 'Session expired; sign in again');\n clearSession(); error = expired; publish();\n return Promise.reject(expired);\n }\n const requestAbort = new AbortController();\n const response = refreshSession({ refresh_token: current.data.refresh_token }, {\n client: publicClient, signal: AbortSignal.any([requestAbort.signal, AbortSignal.timeout(20_000)]),\n });\n const running = { epoch: expected, response, applied: Promise.resolve(), abort: requestAbort };\n flight = running;\n running.applied = response.then(data => {\n assertEpoch(expected);\n validateSession(data);\n if (data.principal.subject !== current.data.principal.subject || data.account.id !== current.data.account.id) {\n throw new AuthError('invalid_response', 'Refresh changed the authenticated account');\n }\n install(data);\n }).catch(failure => {\n if (!disposed && expected === epoch) {\n error = toError(failure);\n if ((failure instanceof ApiError && [401, 403].includes(failure.status)) ||\n failure instanceof AuthError || Date.now() >= current.expiresAt) {\n clearSession();\n } else {\n current.refreshAt = Math.min(current.expiresAt, Date.now() + 5_000);\n schedule(current.refreshAt - Date.now());\n }\n }\n throw failure;\n }).finally(() => {\n if (flight === running) flight = undefined;\n if (!disposed && expected === epoch) publish();\n else if (!disposed && !session) publish();\n });\n publish();\n return running.applied;\n }\n\n async function prepare(provider: string | undefined, settings: SsoOptions = {}): Promise<PreparedSso> {\n assertUsable();\n if (provider !== undefined && (!provider.trim() || provider.length > 64)) throw new TypeError('A registered provider alias is required');\n const returnTo = localReturnTo(settings.returnTo ?? '/', redirect.origin);\n const expected = epoch, operation = Symbol();\n pending.add(operation); error = null; publish();\n try {\n const state = randomSecret(), verifier = randomSecret(), codeChallenge = await challenge(verifier);\n assertEpoch(expected);\n const result = await authorization({\n redirect_uri: redirect.href, code_challenge: codeChallenge, state, provider, prompt: settings.prompt,\n }, { client: publicClient, signal: AbortSignal.any([abort.signal, AbortSignal.timeout(20_000)]) });\n assertEpoch(expected);\n const url = new URL(result.authorizationUrl);\n const suffix = '/protocol/openid-connect/auth';\n if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.hash || !url.pathname.endsWith(suffix) ||\n ['state', 'code_challenge', 'code_challenge_method', 'redirect_uri', 'response_type'].some(key => url.searchParams.getAll(key).length !== 1) ||\n url.searchParams.get('state') !== state || url.searchParams.get('code_challenge') !== codeChallenge ||\n url.searchParams.get('code_challenge_method') !== 'S256' || url.searchParams.get('redirect_uri') !== redirect.href ||\n url.searchParams.get('response_type') !== 'code' || (provider === 'google' && result.provider !== 'google')) {\n throw new AuthError('invalid_response', 'Invalid SSO authorization response');\n }\n const createdAt = Date.now();\n transactions.save(state, { verifier, createdAt, returnTo, provider: result.provider ?? null, issuer: url.origin + url.pathname.slice(0, -suffix.length) });\n return { url: url.href, provider: result.provider ?? null, expiresAt: createdAt + transactionLifetime };\n } catch (failure) {\n if (expected === epoch && !disposed) error = toError(failure);\n throw failure;\n } finally {\n pending.delete(operation);\n if (expected === epoch && !disposed) publish();\n }\n }\n\n function completeSso(callbackUrl?: string): Promise<CompletedSso> {\n try {\n assertUsable();\n const url = parseCallback(callbackUrl ?? (typeof window === 'undefined' ? '' : window.location.href), redirect);\n const state = url.searchParams.get('state') ?? '';\n // Strict Mode can call the default callback again after the first call\n // removed its OAuth parameters, but before the exchange has completed.\n if (callbackUrl === undefined && !state) {\n const pendingCallback = [...callbacks.values()].find(item => item.cleanUrl === url.href);\n if (pendingCallback) return pendingCallback.promise;\n if (completed?.epoch === epoch && completed.url === url.href) return Promise.resolve(completed.result);\n }\n const existing = callbacks.get(state);\n if (existing) {\n if (existing.url !== url.href) throw new AuthError('invalid_callback', 'A different callback is already using this state');\n return existing.promise;\n }\n const cleanUrl = cleanCallback(url);\n const transaction = transactions.consume(state);\n if (url.searchParams.has('iss') && url.searchParams.get('iss') !== transaction.issuer) throw new AuthError('invalid_issuer', 'Unexpected SSO callback issuer');\n if (url.searchParams.has('error')) throw new AuthError('provider_error', 'Sign-in was refused or cancelled by the identity provider');\n const code = url.searchParams.get('code');\n if (!code || code.length > 4096) throw new AuthError('invalid_callback', 'SSO callback is missing a valid authorization code');\n if (session || flight || callbacks.size || pending.size) clearSession();\n const expected = epoch, operation = Symbol();\n pending.add(operation); error = null; publish();\n const completion = (async () => {\n try {\n const data = await createSessionFromCode({ code, codeVerifier: transaction.verifier, redirectUri: redirect.href, state }, {\n client: publicClient, signal: AbortSignal.timeout(20_000),\n });\n if (disposed || expected !== epoch) { await revoke(data.refresh_token).catch(() => {}); throw changed(); }\n validateSession(data);\n if (transaction.provider && data.principal.provider !== transaction.provider) {\n await revoke(data.refresh_token).catch(() => {});\n throw new AuthError('provider_mismatch', 'Sign-in completed with a different identity provider');\n }\n // Anonymous requests made during the callback cannot inherit the new identity.\n clearSession(); install(data);\n const result = { account: data.account, principal: data.principal, returnTo: transaction.returnTo };\n completed = { epoch, url: cleanUrl, result };\n publish();\n return result;\n } catch (failure) {\n if (!disposed && expected === epoch) { error = toError(failure); publish(); }\n throw failure;\n } finally {\n callbacks.delete(state); pending.delete(operation);\n if (!disposed) publish();\n }\n })();\n callbacks.set(state, { url: url.href, cleanUrl, promise: completion });\n return completion;\n } catch (failure) {\n if (!disposed) { error = toError(failure); publish(); }\n return Promise.reject(failure);\n }\n }\n\n return {\n get client() { return client; }, getSnapshot: () => snapshot,\n subscribe(listener) { listeners.add(listener); return () => { listeners.delete(listener); }; },\n prepareInternalSso: settings => prepare('google', settings),\n prepareProviderSso: (alias, settings) => prepare(alias, settings),\n prepareSso: settings => prepare(undefined, settings), completeSso, refresh,\n async logout() {\n assertUsable();\n const old = session, rotating = flight;\n clearSession(); error = null; publish();\n const expected = epoch;\n // Local logout is immediate, even if storage or the revocation request fails.\n let cleanupError: unknown;\n try { transactions.clear(); } catch (failure) { cleanupError = failure; }\n try {\n // A refresh already sent may rotate the token. Revoke its result, not an\n // obsolete token, without ever restoring it to the local session.\n const rotated = await rotating?.response.catch(() => undefined);\n await revoke(rotated?.refresh_token ?? old?.data.refresh_token);\n if (cleanupError) throw cleanupError;\n } catch (failure) {\n if (!disposed && expected === epoch) { error = toError(failure); publish(); }\n throw failure;\n }\n },\n dispose() {\n if (disposed) return;\n flight?.abort.abort();\n clearSession(); disposed = true; error = null; publish(); listeners.clear();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
import { si as AccountRead, sn as PrincipalRead } from "./index.js";
|
|
3
|
+
import { i as ClientOptions, r as Client } from "./client.js";
|
|
4
|
+
//#region src/auth/client.d.ts
|
|
5
|
+
interface MozaikAuthOptions {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
redirectUri: string;
|
|
8
|
+
/** Only PKCE transactions are persisted. Access/refresh tokens stay in memory. */
|
|
9
|
+
transactionStorage?: Storage;
|
|
10
|
+
fetch?: ClientOptions['fetch'];
|
|
11
|
+
}
|
|
12
|
+
interface SsoOptions {
|
|
13
|
+
returnTo?: string;
|
|
14
|
+
prompt?: 'login' | 'none' | 'consent';
|
|
15
|
+
}
|
|
16
|
+
interface PreparedSso {
|
|
17
|
+
url: string;
|
|
18
|
+
provider: string | null;
|
|
19
|
+
expiresAt: number;
|
|
20
|
+
}
|
|
21
|
+
interface CompletedSso {
|
|
22
|
+
account: AccountRead;
|
|
23
|
+
principal: PrincipalRead;
|
|
24
|
+
returnTo: string;
|
|
25
|
+
}
|
|
26
|
+
interface AuthSnapshot {
|
|
27
|
+
status: 'anonymous' | 'authenticating' | 'authenticated' | 'refreshing';
|
|
28
|
+
isAuthenticated: boolean;
|
|
29
|
+
isLoading: boolean;
|
|
30
|
+
account: AccountRead | null;
|
|
31
|
+
principal: PrincipalRead | null;
|
|
32
|
+
error: Error | null;
|
|
33
|
+
/** Stable across refreshes; replaced on login/logout to isolate account caches. */
|
|
34
|
+
client: Client;
|
|
35
|
+
}
|
|
36
|
+
interface MozaikAuth {
|
|
37
|
+
readonly client: Client;
|
|
38
|
+
getSnapshot(): AuthSnapshot;
|
|
39
|
+
subscribe(listener: () => void): () => void;
|
|
40
|
+
prepareInternalSso(options?: SsoOptions): Promise<PreparedSso>;
|
|
41
|
+
prepareProviderSso(alias: string, options?: SsoOptions): Promise<PreparedSso>;
|
|
42
|
+
prepareSso(options?: SsoOptions): Promise<PreparedSso>;
|
|
43
|
+
completeSso(callbackUrl?: string): Promise<CompletedSso>;
|
|
44
|
+
refresh(): Promise<void>;
|
|
45
|
+
logout(): Promise<void>;
|
|
46
|
+
/** Local teardown only. Use logout() to revoke the server session. */
|
|
47
|
+
dispose(): void;
|
|
48
|
+
}
|
|
49
|
+
/** Browser SSO through Mozaik's account-provisioning backend, with no client secret. */
|
|
50
|
+
declare function createMozaikAuth(options: MozaikAuthOptions): MozaikAuth;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { PreparedSso as a, MozaikAuthOptions as i, CompletedSso as n, SsoOptions as o, MozaikAuth as r, createMozaikAuth as s, AuthSnapshot as t };
|
|
53
|
+
//# sourceMappingURL=client2.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { c as request, i as ClientOptions, o as RequestOptions, r as Client, s as createClient, t as ApiError } from "./chunks/client.js";
|
|
3
3
|
//#region src/metadata.d.ts
|
|
4
4
|
export declare const apiVersion = "0.1.0";
|
|
5
|
-
export declare const backendRevision = "
|
|
5
|
+
export declare const backendRevision = "4a542020f6ebb9aa6460023ff6a39cfde89aa82b";
|
|
6
6
|
export declare const schemaSha256 = "991e12e9668ae517fc606191d8374089ebba40e7d21a5394c0d5b6b5d445b22e";
|
|
7
7
|
//#endregion
|
|
8
8
|
export { ApiError, type Client, type ClientOptions, type RequestOptions, createClient, request };
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { n as createClient, r as request, t as ApiError } from "./chunks/client.js";
|
|
3
3
|
//#region src/metadata.ts
|
|
4
4
|
const apiVersion = "0.1.0";
|
|
5
|
-
const backendRevision = "
|
|
5
|
+
const backendRevision = "4a542020f6ebb9aa6460023ff6a39cfde89aa82b";
|
|
6
6
|
const schemaSha256 = "991e12e9668ae517fc606191d8374089ebba40e7d21a5394c0d5b6b5d445b22e";
|
|
7
7
|
//#endregion
|
|
8
8
|
export { ApiError, apiVersion, backendRevision, createClient, request, schemaSha256 };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/metadata.ts"],"sourcesContent":["// Generated from the deployed OpenAPI snapshot.\nexport const apiVersion = \"0.1.0\";\nexport const backendRevision = \"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/metadata.ts"],"sourcesContent":["// Generated from the deployed OpenAPI snapshot.\nexport const apiVersion = \"0.1.0\";\nexport const backendRevision = \"4a542020f6ebb9aa6460023ff6a39cfde89aa82b\";\nexport const schemaSha256 = \"991e12e9668ae517fc606191d8374089ebba40e7d21a5394c0d5b6b5d445b22e\";\n"],"mappings":";;;AACA,MAAa,aAAa;AAC1B,MAAa,kBAAkB;AAC/B,MAAa,eAAe"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import { r as MozaikAuth, t as AuthSnapshot } from "../chunks/client2.js";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
//#region src/auth/react.d.ts
|
|
5
|
+
export type MozaikAuthContext = AuthSnapshot & Pick<MozaikAuth, 'prepareInternalSso' | 'prepareProviderSso' | 'prepareSso' | 'completeSso' | 'refresh' | 'logout'>;
|
|
6
|
+
export interface MozaikAuthProviderProps {
|
|
7
|
+
/** Create once per application (or per SSR request), outside component renders. */
|
|
8
|
+
auth: MozaikAuth;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
/** Provides auth and a TanStack Query cache isolated to the current session. */
|
|
12
|
+
export declare function MozaikAuthProvider({ auth, children }: MozaikAuthProviderProps): import("react").FunctionComponentElement<import("react").ProviderProps<MozaikAuthContext | null>>;
|
|
13
|
+
export declare function useMozaikAuth(): MozaikAuthContext;
|
|
14
|
+
//#endregion
|
|
15
|
+
//# sourceMappingURL=sso.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, createElement, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
3
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
//#region src/auth/react.ts
|
|
5
|
+
const Context = createContext(null);
|
|
6
|
+
let cacheSequence = 0;
|
|
7
|
+
/** Provides auth and a TanStack Query cache isolated to the current session. */
|
|
8
|
+
function MozaikAuthProvider({ auth, children }) {
|
|
9
|
+
const snapshot = useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getSnapshot);
|
|
10
|
+
const cache = useMemo(() => ({
|
|
11
|
+
queryClient: new QueryClient(),
|
|
12
|
+
key: ++cacheSequence,
|
|
13
|
+
lifetime: 0
|
|
14
|
+
}), [auth, snapshot.client]);
|
|
15
|
+
const { queryClient, key } = cache;
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const lifetime = ++cache.lifetime;
|
|
18
|
+
return () => {
|
|
19
|
+
queueMicrotask(() => {
|
|
20
|
+
if (cache.lifetime !== lifetime) return;
|
|
21
|
+
queryClient.cancelQueries();
|
|
22
|
+
queryClient.clear();
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
}, [cache, queryClient]);
|
|
26
|
+
const value = useMemo(() => ({
|
|
27
|
+
...snapshot,
|
|
28
|
+
prepareInternalSso: auth.prepareInternalSso,
|
|
29
|
+
prepareProviderSso: auth.prepareProviderSso,
|
|
30
|
+
prepareSso: auth.prepareSso,
|
|
31
|
+
completeSso: auth.completeSso,
|
|
32
|
+
refresh: auth.refresh,
|
|
33
|
+
logout: auth.logout
|
|
34
|
+
}), [auth, snapshot]);
|
|
35
|
+
return createElement(Context.Provider, { value }, createElement(QueryClientProvider, {
|
|
36
|
+
client: queryClient,
|
|
37
|
+
key
|
|
38
|
+
}, children));
|
|
39
|
+
}
|
|
40
|
+
function useMozaikAuth() {
|
|
41
|
+
const value = useContext(Context);
|
|
42
|
+
if (!value) throw new Error("useMozaikAuth must be used inside MozaikAuthProvider");
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { MozaikAuthProvider, useMozaikAuth };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=sso.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sso.js","names":[],"sources":["../../src/auth/react.ts"],"sourcesContent":["import { createContext, createElement, useContext, useEffect, useMemo, useSyncExternalStore, type ReactNode } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport type { AuthSnapshot, MozaikAuth } from './client';\n\nexport type MozaikAuthContext = AuthSnapshot & Pick<MozaikAuth,\n 'prepareInternalSso' | 'prepareProviderSso' | 'prepareSso' | 'completeSso' | 'refresh' | 'logout'>;\nconst Context = createContext<MozaikAuthContext | null>(null);\nlet cacheSequence = 0;\n\nexport interface MozaikAuthProviderProps {\n /** Create once per application (or per SSR request), outside component renders. */\n auth: MozaikAuth;\n children?: ReactNode;\n}\n\n/** Provides auth and a TanStack Query cache isolated to the current session. */\nexport function MozaikAuthProvider({ auth, children }: MozaikAuthProviderProps) {\n const snapshot = useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getSnapshot);\n // New account/anonymous state gets a new cache before children render. An\n // effect clearing a shared cache would permit one render of the old account.\n const cache = useMemo(() => ({ queryClient: new QueryClient(), key: ++cacheSequence, lifetime: 0 }), [auth, snapshot.client]);\n const { queryClient, key } = cache;\n useEffect(() => {\n const lifetime = ++cache.lifetime;\n return () => {\n // Child observers unsubscribe during cleanup and may schedule GC timers.\n // Clear afterwards; Strict Mode's immediate re-setup keeps its cache.\n queueMicrotask(() => {\n if (cache.lifetime !== lifetime) return;\n void queryClient.cancelQueries(); queryClient.clear();\n });\n };\n }, [cache, queryClient]);\n const value = useMemo<MozaikAuthContext>(() => ({\n ...snapshot,\n prepareInternalSso: auth.prepareInternalSso, prepareProviderSso: auth.prepareProviderSso,\n prepareSso: auth.prepareSso, completeSso: auth.completeSso, refresh: auth.refresh, logout: auth.logout,\n }), [auth, snapshot]);\n // Query observers bind to their initial QueryClient. Remount the session\n // subtree too, so an existing observer cannot retain the previous cache.\n return createElement(Context.Provider, { value }, createElement(QueryClientProvider, { client: queryClient, key }, children));\n}\n\nexport function useMozaikAuth(): MozaikAuthContext {\n const value = useContext(Context);\n if (!value) throw new Error('useMozaikAuth must be used inside MozaikAuthProvider');\n return value;\n}\n"],"mappings":";;;;AAMA,MAAM,UAAU,cAAwC,IAAI;AAC5D,IAAI,gBAAgB;;AASpB,SAAgB,mBAAmB,EAAE,MAAM,YAAqC;CAC9E,MAAM,WAAW,qBAAqB,KAAK,WAAW,KAAK,aAAa,KAAK,WAAW;CAGxF,MAAM,QAAQ,eAAe;EAAE,aAAa,IAAI,YAAY;EAAG,KAAK,EAAE;EAAe,UAAU;CAAE,IAAI,CAAC,MAAM,SAAS,MAAM,CAAC;CAC5H,MAAM,EAAE,aAAa,QAAQ;CAC7B,gBAAgB;EACd,MAAM,WAAW,EAAE,MAAM;EACzB,aAAa;GAGX,qBAAqB;IACnB,IAAI,MAAM,aAAa,UAAU;IACjC,YAAiB,cAAc;IAAG,YAAY,MAAM;GACtD,CAAC;EACH;CACF,GAAG,CAAC,OAAO,WAAW,CAAC;CACvB,MAAM,QAAQ,eAAkC;EAC9C,GAAG;EACH,oBAAoB,KAAK;EAAoB,oBAAoB,KAAK;EACtE,YAAY,KAAK;EAAY,aAAa,KAAK;EAAa,SAAS,KAAK;EAAS,QAAQ,KAAK;CAClG,IAAI,CAAC,MAAM,QAAQ,CAAC;CAGpB,OAAO,cAAc,QAAQ,UAAU,EAAE,MAAM,GAAG,cAAc,qBAAqB;EAAE,QAAQ;EAAa;CAAI,GAAG,QAAQ,CAAC;AAC9H;AAEA,SAAgB,gBAAmC;CACjD,MAAM,QAAQ,WAAW,OAAO;CAChC,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,sDAAsD;CAClF,OAAO;AACT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learnorama/mozaik-sdk",
|
|
3
|
-
"version": "0.1.0-staging.
|
|
3
|
+
"version": "0.1.0-staging.13.1",
|
|
4
4
|
"description": "Typed Mozaik API client and optional TanStack Query hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
"types": "./dist/chat.d.ts",
|
|
28
28
|
"import": "./dist/chat.js"
|
|
29
29
|
},
|
|
30
|
+
"./auth": {
|
|
31
|
+
"types": "./dist/auth.d.ts",
|
|
32
|
+
"import": "./dist/auth.js"
|
|
33
|
+
},
|
|
34
|
+
"./react/sso": {
|
|
35
|
+
"types": "./dist/react/sso.d.ts",
|
|
36
|
+
"import": "./dist/react/sso.js"
|
|
37
|
+
},
|
|
30
38
|
"./react/chat": {
|
|
31
39
|
"types": "./dist/react/chat.d.ts",
|
|
32
40
|
"import": "./dist/react/chat.js"
|
|
@@ -65,14 +73,14 @@
|
|
|
65
73
|
},
|
|
66
74
|
"mozaik": {
|
|
67
75
|
"apiVersion": "0.1.0",
|
|
68
|
-
"backendRevision": "
|
|
76
|
+
"backendRevision": "4a542020f6ebb9aa6460023ff6a39cfde89aa82b",
|
|
69
77
|
"schemaSha256": "991e12e9668ae517fc606191d8374089ebba40e7d21a5394c0d5b6b5d445b22e",
|
|
70
78
|
"sdkFingerprintVersion": 2,
|
|
71
|
-
"sdkFingerprint": "
|
|
79
|
+
"sdkFingerprint": "85c8b55560bc78f9b426ffeb2d5d7d10d7585c2a3e293c27e0d90e9e14705015",
|
|
72
80
|
"sdkContent": {
|
|
73
|
-
"runtime": "
|
|
74
|
-
"types": "
|
|
75
|
-
"package": "
|
|
81
|
+
"runtime": "d2eb442e13ac3d0102d763c69a0c2cebceaa5bd58da1e2a3df1382f1cf6929ee",
|
|
82
|
+
"types": "8dfdefdeef650b3491da6a6408d7af210fe9f12d848af5e6d23bdbc094bd1946",
|
|
83
|
+
"package": "99a714f26e86b40c6eabd5248366c7c0efb397f4a6e7cfb7adbca689382e2f11"
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
}
|