@ai-matrx/auth 0.2.0 → 0.2.2
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/CHANGELOG.md +47 -0
- package/dist/active-organization-Dy4UtiyR.d.cts +594 -0
- package/dist/active-organization-Dy4UtiyR.d.ts +594 -0
- package/dist/guest.cjs +213 -0
- package/dist/guest.d.cts +189 -0
- package/dist/guest.d.ts +189 -0
- package/dist/guest.js +192 -0
- package/dist/index.cjs +514 -112
- package/dist/index.d.cts +161 -5
- package/dist/index.d.ts +161 -5
- package/dist/index.js +515 -113
- package/dist/matrx-oauth-_Ji8X5Wm.d.cts +264 -0
- package/dist/matrx-oauth-_Ji8X5Wm.d.ts +264 -0
- package/dist/matrx-oauth.cjs +77 -10
- package/dist/matrx-oauth.d.cts +1 -181
- package/dist/matrx-oauth.d.ts +1 -181
- package/dist/matrx-oauth.js +77 -10
- package/dist/organizations.cjs +269 -13
- package/dist/organizations.d.cts +3 -225
- package/dist/organizations.d.ts +3 -225
- package/dist/organizations.js +269 -13
- package/dist/react.d.cts +3 -1
- package/dist/react.d.ts +3 -1
- package/package.json +12 -2
- package/dist/session-DdQiStp6.d.cts +0 -112
- package/dist/session-DdQiStp6.d.ts +0 -112
- package/dist/storage-Dqnye4KF.d.cts +0 -51
- package/dist/storage-Dqnye4KF.d.ts +0 -51
package/dist/guest.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// src/guest.ts
|
|
2
|
+
import { createServerClient } from "@supabase/ssr";
|
|
3
|
+
var VISITOR_ID = /^[A-Za-z0-9]{16,200}$/;
|
|
4
|
+
function looksLikeVisitorId(id) {
|
|
5
|
+
return typeof id === "string" && !id.startsWith("temp_") && VISITOR_ID.test(id);
|
|
6
|
+
}
|
|
7
|
+
function isGuestCookieName(guestCookieName, name) {
|
|
8
|
+
return name === guestCookieName || name.startsWith(`${guestCookieName}.`);
|
|
9
|
+
}
|
|
10
|
+
var DEFAULT_GUEST_CARRIER_COOKIE = "matrx_guest_visitor";
|
|
11
|
+
function createGuestHandover(options) {
|
|
12
|
+
const carrier = options.carrierCookieName ?? DEFAULT_GUEST_CARRIER_COOKIE;
|
|
13
|
+
const loud = options.log ?? ((m) => console.error(`[session-handover] LOUD: ${m}`));
|
|
14
|
+
const isGuestCookie = (name) => isGuestCookieName(options.guestCookieName, name);
|
|
15
|
+
const msg = (err) => err instanceof Error ? err.message : String(err);
|
|
16
|
+
async function readClaims(jar) {
|
|
17
|
+
if (options.readGuestClaims) return options.readGuestClaims(jar);
|
|
18
|
+
if (!options.supabase) return null;
|
|
19
|
+
const guest = createServerClient(options.supabase.url, options.supabase.publishableKey, {
|
|
20
|
+
cookieOptions: { name: options.guestCookieName },
|
|
21
|
+
cookies: { getAll: () => jar.getAll(), setAll: () => void 0 }
|
|
22
|
+
});
|
|
23
|
+
const { data } = await guest.auth.getClaims();
|
|
24
|
+
return data?.claims ?? null;
|
|
25
|
+
}
|
|
26
|
+
async function readGuestPossession(visitorId) {
|
|
27
|
+
const jar = await options.cookies();
|
|
28
|
+
if (jar.getAll().some((c) => isGuestCookie(c.name))) {
|
|
29
|
+
try {
|
|
30
|
+
const claims = await readClaims(jar);
|
|
31
|
+
if (claims?.sub && claims.is_anonymous === true) return { guestId: claims.sub, proof: "guest_session" };
|
|
32
|
+
} catch (err) {
|
|
33
|
+
loud(`the guest cookie could not be verified: ${msg(err)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const candidates = [visitorId, options.visitorCookieName ? jar.get(options.visitorCookieName)?.value : void 0].filter(looksLikeVisitorId);
|
|
37
|
+
if (!candidates.length) return null;
|
|
38
|
+
const { data: ids, error } = await options.store.findGuestIdsByVisitor(candidates);
|
|
39
|
+
if (error) {
|
|
40
|
+
loud(`the visitor's guest could not be looked up: ${error.message}`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
for (const id of ids ?? []) {
|
|
44
|
+
if (await options.store.isAnonymousUser(id)) return { guestId: id, proof: "visitor_id" };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
async function dropGuestCookies() {
|
|
49
|
+
try {
|
|
50
|
+
const jar = await options.cookies();
|
|
51
|
+
for (const c of jar.getAll()) if (isGuestCookie(c.name)) jar.delete(c.name);
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function claimGuestInto(guest, accountId, via) {
|
|
56
|
+
if (guest.guestId === accountId) return { status: "same_account" };
|
|
57
|
+
const { data, error } = await options.store.claimWorkspace({ guestId: guest.guestId, targetId: accountId, via });
|
|
58
|
+
if (error) {
|
|
59
|
+
loud(`the guest's records were NOT brought into ${accountId} (${via}); the guest cookie is kept so the next sign-in retries: ${error.message}`);
|
|
60
|
+
return { status: "failed", guestId: guest.guestId, message: error.message };
|
|
61
|
+
}
|
|
62
|
+
await dropGuestCookies();
|
|
63
|
+
const records = typeof data?.records === "number" ? data.records : 0;
|
|
64
|
+
return { status: "claimed", guestId: guest.guestId, records };
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
looksLikeVisitorId,
|
|
68
|
+
readGuestPossession,
|
|
69
|
+
claimGuestInto,
|
|
70
|
+
async handOverSession(via, replace, opts = {}) {
|
|
71
|
+
const guest = await readGuestPossession(opts.visitorId);
|
|
72
|
+
const result = await replace();
|
|
73
|
+
const user = result.error ? null : result.data?.user ?? null;
|
|
74
|
+
let guestClaim = { status: "none" };
|
|
75
|
+
if (guest && user && user.is_anonymous !== true) guestClaim = await claimGuestInto(guest, user.id, via);
|
|
76
|
+
return { ...result, guestClaim };
|
|
77
|
+
},
|
|
78
|
+
async settlePendingGuestClaim(accountId) {
|
|
79
|
+
const jar = await options.cookies();
|
|
80
|
+
if (!jar.getAll().some((c) => isGuestCookie(c.name))) return { status: "none" };
|
|
81
|
+
const guest = await readGuestPossession(null);
|
|
82
|
+
if (!guest || guest.proof !== "guest_session") return { status: "none" };
|
|
83
|
+
return claimGuestInto(guest, accountId, "password_login");
|
|
84
|
+
},
|
|
85
|
+
async promoteGuest(args) {
|
|
86
|
+
const guest = await readGuestPossession(args.visitorId);
|
|
87
|
+
if (!guest) return { kind: "no_guest" };
|
|
88
|
+
const revive = async () => {
|
|
89
|
+
const { error } = await options.store.revive(guest.guestId);
|
|
90
|
+
if (error) throw new Error(`the guest could not be reopened: ${error.message}`);
|
|
91
|
+
};
|
|
92
|
+
try {
|
|
93
|
+
await revive();
|
|
94
|
+
const { error } = await options.store.promoteUser(guest.guestId, { email: args.email, password: args.password });
|
|
95
|
+
if (error) {
|
|
96
|
+
const m = error.message.toLowerCase();
|
|
97
|
+
if (error.code === "email_exists" || m.includes("already been registered") || m.includes("already registered") || m.includes("already exists")) {
|
|
98
|
+
return { kind: "email_in_use" };
|
|
99
|
+
}
|
|
100
|
+
loud(`guest promotion failed \u2014 the guest is kept, no new account is made: ${error.message}`);
|
|
101
|
+
return { kind: "failed", message: error.message };
|
|
102
|
+
}
|
|
103
|
+
await revive();
|
|
104
|
+
const { error: stampErr } = await options.store.stampConverted(guest.guestId);
|
|
105
|
+
if (stampErr) loud(`promotion succeeded but the registry row was not stamped: ${stampErr.message}`);
|
|
106
|
+
return { kind: "promoted", userId: guest.guestId };
|
|
107
|
+
} catch (err) {
|
|
108
|
+
loud(`guest promotion failed \u2014 the guest is kept: ${msg(err)}`);
|
|
109
|
+
return { kind: "failed", message: msg(err) };
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
finishPromotion: dropGuestCookies,
|
|
113
|
+
async rememberGuestVisitorId(formData) {
|
|
114
|
+
try {
|
|
115
|
+
const id = formData?.get("guestFingerprint")?.toString();
|
|
116
|
+
if (!looksLikeVisitorId(id)) return;
|
|
117
|
+
(await options.cookies()).set(carrier, id, {
|
|
118
|
+
httpOnly: true,
|
|
119
|
+
sameSite: "lax",
|
|
120
|
+
secure: options.secureCookies ?? false,
|
|
121
|
+
path: "/",
|
|
122
|
+
maxAge: 600
|
|
123
|
+
});
|
|
124
|
+
} catch (err) {
|
|
125
|
+
loud(`the visitor id could not be kept for the OAuth round trip: ${msg(err)}`);
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
async takeGuestVisitorId() {
|
|
129
|
+
const jar = await options.cookies();
|
|
130
|
+
const id = jar.get(carrier)?.value ?? null;
|
|
131
|
+
if (id) {
|
|
132
|
+
try {
|
|
133
|
+
jar.delete(carrier);
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return looksLikeVisitorId(id) ? id : null;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function guestClientCookieOptions(cookieName, secure = typeof window !== "undefined" && window.location.protocol === "https:") {
|
|
142
|
+
return { name: cookieName, path: "/", sameSite: "lax", secure, maxAge: 60 * 60 * 24 * 400 };
|
|
143
|
+
}
|
|
144
|
+
function createGuestSessionEnsurer(options) {
|
|
145
|
+
const doFetch = options.fetch ?? fetch;
|
|
146
|
+
let inFlight = null;
|
|
147
|
+
async function existing() {
|
|
148
|
+
const { data } = await options.auth.getSession();
|
|
149
|
+
const user = data.session?.user;
|
|
150
|
+
if (!user?.is_anonymous) return null;
|
|
151
|
+
const orgs = [...new Set((await options.listWorkspaceIds(user.id)).filter((o) => typeof o === "string"))];
|
|
152
|
+
return orgs.length === 1 ? { organizationId: orgs[0], adoptedFrom: null } : null;
|
|
153
|
+
}
|
|
154
|
+
async function mint() {
|
|
155
|
+
const have = await existing();
|
|
156
|
+
if (have) return have;
|
|
157
|
+
const base = options.serverBaseUrl;
|
|
158
|
+
if (!base) throw new Error("The AI Matrx server address is not configured, so a guest session cannot be made.");
|
|
159
|
+
const fingerprint = await options.getFingerprint();
|
|
160
|
+
const res = await doFetch(`${base.replace(/\/$/, "")}/auth/guest/session`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: { "Content-Type": "application/json" },
|
|
163
|
+
body: JSON.stringify({ fingerprint })
|
|
164
|
+
});
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
const body = await res.json().catch(() => null);
|
|
167
|
+
throw new Error(body?.detail?.message ?? `A guest session could not be made (HTTP ${res.status}).`);
|
|
168
|
+
}
|
|
169
|
+
const s = await res.json();
|
|
170
|
+
const { error } = await options.auth.setSession({ access_token: s.access_token, refresh_token: s.refresh_token });
|
|
171
|
+
if (error) throw new Error(`The guest session could not be kept: ${error.message}`);
|
|
172
|
+
if (!s.organization_id) throw new Error("The guest session has no workspace to save in.");
|
|
173
|
+
return { organizationId: s.organization_id, adoptedFrom: s.adopted_from };
|
|
174
|
+
}
|
|
175
|
+
return () => {
|
|
176
|
+
inFlight ??= (async () => {
|
|
177
|
+
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
178
|
+
return locks ? locks.request(options.lockName ?? "matrx-guest-session", () => mint()) : mint();
|
|
179
|
+
})().finally(() => {
|
|
180
|
+
inFlight = null;
|
|
181
|
+
});
|
|
182
|
+
return inFlight;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
DEFAULT_GUEST_CARRIER_COOKIE,
|
|
187
|
+
createGuestHandover,
|
|
188
|
+
createGuestSessionEnsurer,
|
|
189
|
+
guestClientCookieOptions,
|
|
190
|
+
isGuestCookieName,
|
|
191
|
+
looksLikeVisitorId
|
|
192
|
+
};
|