@voltro/plugin-auth-social 0.86.0 → 0.88.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.
@@ -0,0 +1,60 @@
1
+ import { SocialConfigError as e } from "./errors.js";
2
+ import { isSocialProviderId as t } from "./providers.js";
3
+ import { signAuthClaims as n, verifyAuthClaims as r } from "@voltro/plugin-auth";
4
+ // src/guestHandoff.ts
5
+ var i = (t, r) => {
6
+ let i = t.profile, a = n({
7
+ exp: t.exp,
8
+ audience: t.audience,
9
+ guest: t.guest,
10
+ profile: {
11
+ provider: i.provider,
12
+ providerAccountId: i.providerAccountId,
13
+ email: i.email,
14
+ emailVerified: i.emailVerified,
15
+ emailIsPrivateRelay: i.emailIsPrivateRelay,
16
+ firstName: i.firstName,
17
+ lastName: i.lastName,
18
+ nameIsFirstAuthorizationOnly: i.nameIsFirstAuthorizationOnly
19
+ }
20
+ }, r.current.secret, "social-upgrade");
21
+ if (a.length > 3400) throw new e("Guest upgrade handoff exceeds its cookie budget");
22
+ return a;
23
+ }, a = (e) => typeof e == "object" && e && !Array.isArray(e) ? e : null, o = (e) => {
24
+ let t = a(e), n = a(t?.metadata);
25
+ return t?.type !== "user" || t.kind !== "guest" || typeof t.id != "string" || !/^guest_[A-Za-z0-9_-]+$/.test(t.id) || typeof t.tenantId != "string" || !t.tenantId.trim() || typeof n?.sessionId != "string" || !n.sessionId || typeof n.guestDeviceId != "string" || !/^gdev_[A-Za-z0-9_-]+$/.test(n.guestDeviceId) || typeof n.guestDeviceGeneration != "number" || !Number.isSafeInteger(n.guestDeviceGeneration) || n.guestDeviceGeneration < 0 ? null : {
26
+ type: "user",
27
+ kind: "guest",
28
+ id: t.id,
29
+ tenantId: t.tenantId,
30
+ metadata: {
31
+ sessionId: n.sessionId,
32
+ guestDeviceId: n.guestDeviceId,
33
+ guestDeviceGeneration: n.guestDeviceGeneration
34
+ }
35
+ };
36
+ }, s = (e, n, i) => {
37
+ if (!e || e.length > 3400) return null;
38
+ let s = [n.current, n.previous].filter((e) => e !== void 0).map((t) => r(e, t.secret, "social-upgrade")).find((e) => e !== null);
39
+ if (!s || s.audience !== i || typeof s.exp != "number") return null;
40
+ let c = o(s.guest), l = a(s.profile), u = (e) => e === null || typeof e == "string";
41
+ return !c || !l || typeof l.provider != "string" || !t(l.provider) || typeof l.providerAccountId != "string" || !l.providerAccountId || !u(l.email) || !u(l.firstName) || !u(l.lastName) || typeof l.emailVerified != "boolean" || typeof l.emailIsPrivateRelay != "boolean" || typeof l.nameIsFirstAuthorizationOnly != "boolean" ? null : {
42
+ guest: c,
43
+ exp: s.exp,
44
+ audience: i,
45
+ profile: {
46
+ provider: l.provider,
47
+ providerAccountId: l.providerAccountId,
48
+ email: l.email,
49
+ emailVerified: l.emailVerified,
50
+ emailIsPrivateRelay: l.emailIsPrivateRelay,
51
+ firstName: l.firstName,
52
+ lastName: l.lastName,
53
+ nameIsFirstAuthorizationOnly: l.nameIsFirstAuthorizationOnly,
54
+ name: null,
55
+ avatarUrl: null,
56
+ raw: {}
57
+ }
58
+ };
59
+ };
60
+ export { s as decodeGuestUpgradeHandoff, i as encodeGuestUpgradeHandoff, o as guestIdentityOf };
@@ -0,0 +1,96 @@
1
+ import { SOCIAL_IDENTITY_TABLE as e, dataStoreSocialIdentityStore as t } from "./identityStore.js";
2
+ import { Effect as n } from "effect";
3
+ import { and as r, eq as i } from "@voltro/database";
4
+ import { GuestAuthError as a, dataStoreGuestAuth as o, dataStoreSessionRegistry as s, issueUserSession as c, runSubjectGuards as l } from "@voltro/plugin-auth";
5
+ // src/guestUpgrade.ts
6
+ var u = (e, t, n) => ({
7
+ table: e,
8
+ predicate: i(t, n),
9
+ order: [],
10
+ take: void 0,
11
+ skip: void 0,
12
+ projection: void 0
13
+ }), d = (d) => {
14
+ if (d.config.defaultTenantId !== d.guest.tenantId || d.config.ensureActor !== void 0 && d.configForTransaction === void 0) return n.succeed({
15
+ kind: "refused",
16
+ status: 500,
17
+ code: "config",
18
+ message: "Guest upgrade requires a matching tenant and transaction-bound actor hooks."
19
+ });
20
+ let f = d.profile, p = f.email === null || f.email.length === 0 ? null : f.email;
21
+ return (d.service ?? o(d.store, d.guest)).upgrade(d.credential, async (o) => {
22
+ let m = o.store, h = t(m);
23
+ if (await n.runPromise(h.findByProviderAccount(f.provider, f.providerAccountId)) !== null || p !== null && (await m.query(u("users", "email", p))).length !== 0) throw new a({ code: "upgrade_conflict" });
24
+ let g = {
25
+ id: o.subjectId,
26
+ tenantId: o.tenantId,
27
+ email: p,
28
+ passwordHash: null,
29
+ createdAt: o.createdAt,
30
+ mfaSecret: null,
31
+ mfaEnrolledAt: null,
32
+ emailVerifiedAt: p !== null && f.emailVerified ? /* @__PURE__ */ new Date() : null
33
+ }, _ = d.configForTransaction?.(m) ?? d.config;
34
+ if (_.defaultTenantId !== o.tenantId || !(await n.runPromise(l(_.subjectGuards ?? [], g))).ok) throw new a({ code: "credential_rejected" });
35
+ if (await m.insert("users", {
36
+ ...g,
37
+ updatedAt: /* @__PURE__ */ new Date()
38
+ }), !await n.runPromise(h.insert({
39
+ userId: g.id,
40
+ provider: f.provider,
41
+ providerAccountId: f.providerAccountId,
42
+ email: p,
43
+ emailVerified: p !== null && f.emailVerified
44
+ }).pipe(n.as(!0), n.catchTag("SocialIdentityConflict", () => n.succeed(!1))))) throw new a({ code: "upgrade_conflict" });
45
+ let v = {
46
+ ...d.users,
47
+ ...s(m),
48
+ listMemberships: (e) => n.promise(async () => (await m.query({
49
+ ...u("memberships", "userId", e),
50
+ predicate: r(i("userId", e), i("tenantId", o.tenantId))
51
+ })).map((t) => {
52
+ if (t.userId !== e || t.tenantId !== o.tenantId || typeof t.role != "string") throw new a({ code: "store_unavailable" });
53
+ let n = t.joinedAt instanceof Date ? t.joinedAt : new Date(String(t.joinedAt));
54
+ if (!Number.isFinite(n.getTime())) throw new a({ code: "store_unavailable" });
55
+ return {
56
+ userId: e,
57
+ tenantId: o.tenantId,
58
+ role: t.role,
59
+ joinedAt: n
60
+ };
61
+ }))
62
+ }, y = await n.runPromise(c(g, v, _, {
63
+ clearLoginFailures: !1,
64
+ ipAddress: d.ipAddress ?? null,
65
+ userAgent: d.userAgent ?? null
66
+ })), b = y.subject.type === "user" ? y.subject.metadata?.sessionId : null;
67
+ if (typeof b != "string" || (await m.query(u("sessions", "id", b))).length !== 1) throw new a({ code: "store_unavailable" });
68
+ let x = (await m.query({
69
+ ...u(e, "provider", f.provider),
70
+ predicate: r(i("provider", f.provider), i("providerAccountId", f.providerAccountId))
71
+ }))[0];
72
+ if (!x || x.provider !== f.provider || x.providerAccountId !== f.providerAccountId || x.userId !== g.id) throw new a({ code: "upgrade_conflict" });
73
+ return {
74
+ kind: "ok",
75
+ userId: g.id,
76
+ subject: y.subject,
77
+ setCookie: y.setCookie,
78
+ session: {
79
+ value: y.value,
80
+ expiresAt: y.expiresAt
81
+ },
82
+ created: !0,
83
+ linked: !0,
84
+ oneTimeName: f.nameIsFirstAuthorizationOnly ? {
85
+ firstName: f.firstName,
86
+ lastName: f.lastName
87
+ } : null
88
+ };
89
+ }).pipe(n.catch((e) => n.succeed({
90
+ kind: "refused",
91
+ status: e.code === "upgrade_conflict" ? 409 : e.code === "credential_rejected" ? 403 : 503,
92
+ code: e.code === "upgrade_conflict" ? "account_merge_required" : e.code,
93
+ message: e.code === "upgrade_conflict" ? "The provider or email belongs to an existing account. Confirm both accounts before merging." : e.code === "credential_rejected" ? "Guest upgrade was refused." : "Guest upgrade storage is unavailable."
94
+ })));
95
+ };
96
+ export { d as completeGuestSocialUpgrade };
@@ -1,104 +1,123 @@
1
- import { Effect as e } from "effect";
2
- import { and as t, boolean as n, eq as r, generateId as i, id as a, table as o, text as s, timestamp as c } from "@voltro/database";
3
- //#region src/identityStore.ts
4
- var l = "_voltro_oauth_identities", u = o(l, {
5
- id: a({ prefix: "oauthid" }),
6
- userId: s(),
7
- provider: s(),
8
- providerAccountId: s(),
9
- email: s().nullable(),
10
- emailVerified: n().default(!1),
11
- createdAt: c().default("now"),
12
- lastLoginAt: c().nullable()
13
- }).unique("byOauthProviderAccount", ["provider", "providerAccountId"]).index(["userId"]), d = [u], f = () => i({
1
+ import { Data as e, Effect as t } from "effect";
2
+ import { and as n, boolean as r, classifyConstraintViolation as i, eq as a, extractDbCause as o, generateId as s, id as c, table as l, text as u, timestamp as d } from "@voltro/database";
3
+ // src/identityStore.ts
4
+ var f = "_voltro_oauth_identities", p = l(f, {
5
+ id: c({ prefix: "oauthid" }),
6
+ userId: u(),
7
+ provider: u(),
8
+ providerAccountId: u(),
9
+ email: u().nullable(),
10
+ emailVerified: r().default(!1),
11
+ createdAt: d().default("now"),
12
+ lastLoginAt: d().nullable()
13
+ }).unique("byOauthProviderAccount", ["provider", "providerAccountId"]).index(["userId"]), m = [p], h = class extends e.TaggedError("SocialIdentityConflict") {}, g = (e) => typeof e == "object" && !!e && e._tag === "ConstraintViolation" && e.kind === "unique" || i(o(e), "insert")?.kind === "unique", _ = () => s({
14
14
  kind: "typeid",
15
15
  prefix: "oauthid"
16
- }, "oauthid"), p = (t = []) => {
17
- let n = [...t];
16
+ }, "oauthid"), v = (e = []) => {
17
+ let n = [...e];
18
18
  return {
19
- findByProviderAccount: (t, r) => e.sync(() => n.find((e) => e.provider === t && e.providerAccountId === r) ?? null),
20
- listForUser: (t) => e.sync(() => n.filter((e) => e.userId === t)),
21
- insert: (t) => e.sync(() => {
22
- let e = {
23
- ...t,
24
- id: f(),
19
+ findByProviderAccount: (e, r) => t.sync(() => n.find((t) => t.provider === e && t.providerAccountId === r) ?? null),
20
+ listForUser: (e) => t.sync(() => n.filter((t) => t.userId === e)),
21
+ insert: (e) => n.some((t) => t.provider === e.provider && t.providerAccountId === e.providerAccountId) ? t.fail(new h({ provider: e.provider })) : t.sync(() => {
22
+ let t = {
23
+ ...e,
24
+ id: _(),
25
25
  createdAt: /* @__PURE__ */ new Date(),
26
26
  lastLoginAt: null
27
27
  };
28
- return n.push(e), e;
28
+ return n.push(t), t;
29
29
  }),
30
- touch: (t, r) => e.sync(() => {
31
- let e = n.findIndex((e) => e.id === t);
32
- e >= 0 && (n[e] = {
33
- ...n[e],
30
+ touch: (e, r) => t.sync(() => {
31
+ let t = n.findIndex((t) => t.id === e);
32
+ t >= 0 && (n[t] = {
33
+ ...n[t],
34
34
  lastLoginAt: r
35
35
  });
36
36
  }),
37
- remove: (t, r) => e.sync(() => {
38
- let e = n.findIndex((e) => e.userId === t && e.provider === r);
39
- return e < 0 ? !1 : (n.splice(e, 1), !0);
37
+ remove: (e, r) => t.sync(() => {
38
+ let t = n.findIndex((t) => t.userId === e && t.provider === r);
39
+ return t < 0 ? !1 : (n.splice(t, 1), !0);
40
40
  })
41
41
  };
42
- }, m = (e) => {
42
+ }, y = (e) => {
43
43
  if (e instanceof Date) return e;
44
44
  if (typeof e == "string" || typeof e == "number") {
45
45
  let t = new Date(e);
46
46
  return Number.isNaN(t.getTime()) ? null : t;
47
47
  }
48
48
  return null;
49
- }, h = (e) => ({
49
+ }, b = (e) => ({
50
50
  id: String(e.id ?? ""),
51
51
  userId: String(e.userId ?? ""),
52
52
  provider: String(e.provider ?? ""),
53
53
  providerAccountId: String(e.providerAccountId ?? ""),
54
54
  email: typeof e.email == "string" && e.email.length > 0 ? e.email : null,
55
55
  emailVerified: e.emailVerified === !0 || e.emailVerified === 1,
56
- createdAt: m(e.createdAt) ?? /* @__PURE__ */ new Date(0),
57
- lastLoginAt: m(e.lastLoginAt)
58
- }), g = (n) => ({
59
- findByProviderAccount: (i, a) => e.promise(async () => {
60
- let e = (await n.query({
61
- table: l,
62
- predicate: t(r("provider", i), r("providerAccountId", a)),
63
- limit: 1
56
+ createdAt: y(e.createdAt) ?? /* @__PURE__ */ new Date(0),
57
+ lastLoginAt: y(e.lastLoginAt)
58
+ }), x = (e) => ({
59
+ findByProviderAccount: (r, i) => t.promise(async () => {
60
+ let t = (await e.query({
61
+ table: f,
62
+ predicate: n(a("provider", r), a("providerAccountId", i)),
63
+ limit: 1,
64
+ take: 1,
65
+ skip: void 0,
66
+ projection: void 0,
67
+ order: []
64
68
  }))[0];
65
- return e ? h(e) : null;
69
+ return t ? b(t) : null;
66
70
  }),
67
- listForUser: (t) => e.promise(async () => (await n.query({
68
- table: l,
69
- predicate: r("userId", t),
71
+ listForUser: (n) => t.promise(async () => (await e.query({
72
+ table: f,
73
+ predicate: a("userId", n),
70
74
  order: [{
71
75
  column: "createdAt",
72
76
  direction: "asc"
73
- }]
74
- })).map(h)),
75
- insert: (t) => e.promise(async () => h(await n.insert(l, {
76
- id: f(),
77
- userId: t.userId,
78
- provider: t.provider,
79
- providerAccountId: t.providerAccountId,
80
- email: t.email,
81
- emailVerified: t.emailVerified,
82
- createdAt: /* @__PURE__ */ new Date(),
83
- lastLoginAt: null
84
- }))),
85
- touch: (t, r) => e.promise(async () => {
86
- await n.update(l, t, { lastLoginAt: r });
77
+ }],
78
+ take: void 0,
79
+ skip: void 0,
80
+ projection: void 0
81
+ })).map(b)),
82
+ insert: (n) => t.tryPromise({
83
+ try: async () => {
84
+ let t = {
85
+ id: _(),
86
+ userId: n.userId,
87
+ provider: n.provider,
88
+ providerAccountId: n.providerAccountId,
89
+ email: n.email,
90
+ emailVerified: n.emailVerified,
91
+ createdAt: /* @__PURE__ */ new Date(),
92
+ lastLoginAt: null
93
+ };
94
+ if (!("insertIgnoreWithOutcome" in e) || e.insertIgnoreWithOutcome === void 0) return b(await e.insert(f, t));
95
+ let r = await e.insertIgnoreWithOutcome(f, t, { conflictColumns: ["provider", "providerAccountId"] });
96
+ if (r.outcome === "ignored") throw new h({ provider: n.provider });
97
+ return b(r.row);
98
+ },
99
+ catch: (e) => e
100
+ }).pipe(t.catch((e) => e instanceof h || g(e) ? t.fail(new h({ provider: n.provider })) : t.die(e))),
101
+ touch: (n, r) => t.promise(async () => {
102
+ await e.update(f, n, { lastLoginAt: r });
87
103
  }),
88
- remove: (i, a) => e.promise(async () => {
89
- let e = (await n.query({
90
- table: l,
91
- predicate: t(r("userId", i), r("provider", a)),
92
- limit: 1
104
+ remove: (r, i) => t.promise(async () => {
105
+ let t = (await e.query({
106
+ table: f,
107
+ predicate: n(a("userId", r), a("provider", i)),
108
+ limit: 1,
109
+ take: 1,
110
+ skip: void 0,
111
+ projection: void 0,
112
+ order: []
93
113
  }))[0];
94
- return e ? n.delete(l, String(e.id ?? "")) : !1;
114
+ return t ? e.delete(f, String(t.id ?? "")) : !1;
95
115
  })
96
- }), _ = (t, n, r) => e.gen(function* () {
97
- let i = yield* t.findByProviderAccount(r.provider, r.providerAccountId);
98
- return i ? i.userId === n ? i : yield* e.fail(/* @__PURE__ */ Error(`linkSocialIdentity: this ${r.provider} account is already linked to another user`)) : yield* t.insert({
116
+ }), S = (e, n, r) => t.gen(function* () {
117
+ let i = yield* e.findByProviderAccount(r.provider, r.providerAccountId);
118
+ return i ? i.userId === n ? i : yield* t.fail(/* @__PURE__ */ Error(`linkSocialIdentity: this ${r.provider} account is already linked to another user`)) : yield* e.insert({
99
119
  ...r,
100
120
  userId: n
101
121
  });
102
122
  });
103
- //#endregion
104
- export { l as SOCIAL_IDENTITY_TABLE, g as dataStoreSocialIdentityStore, _ as linkSocialIdentity, p as memorySocialIdentityStore, d as socialAuthTables, u as socialIdentitiesTable };
123
+ export { f as SOCIAL_IDENTITY_TABLE, h as SocialIdentityConflict, x as dataStoreSocialIdentityStore, S as linkSocialIdentity, v as memorySocialIdentityStore, m as socialAuthTables, p as socialIdentitiesTable };