@supacloud/lite 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.0](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.6.0...supacloud-lite-v0.7.0) (2026-08-11)
4
+
5
+
6
+ ### Features
7
+
8
+ * **lite:** support admin magic link verification ([#788](https://github.com/zuohuadong/supacloud/issues/788)) ([97defa0](https://github.com/zuohuadong/supacloud/commit/97defa0e3ba875d1bd0363f9f64df02a089238ff))
9
+
3
10
  ## [0.6.0](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.5.10...supacloud-lite-v0.6.0) (2026-08-11)
4
11
 
5
12
 
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { dirname as dirname5, join as join9, resolve as resolve4 } from "path";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@supacloud/lite",
11
- version: "0.6.0",
11
+ version: "0.7.0",
12
12
  description: "Bun-native, single-project Supabase-compatible backend powered by PGlite",
13
13
  type: "module",
14
14
  license: "Apache-2.0",
@@ -1112,6 +1112,10 @@ function randomOtp(length) {
1112
1112
  }
1113
1113
  return code;
1114
1114
  }
1115
+ async function sha256Hex(value) {
1116
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
1117
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1118
+ }
1115
1119
  function normalizePhone(value) {
1116
1120
  if (typeof value !== "string")
1117
1121
  return null;
@@ -1650,33 +1654,58 @@ Or sign in with this link: ${link}`
1650
1654
  }
1651
1655
  static MAX_OTP_ATTEMPTS = 5;
1652
1656
  async redeem(token, types, email) {
1653
- const normalizedEmail = email?.toLowerCase().trim() ?? null;
1654
- const res = await this.db.query(`delete from auth.one_time_tokens
1655
- where token = $1 and token_type = any($2::text[])
1656
- and ($3::text is null or email = $3) and expires_at > now()
1657
- and attempts < $4
1658
- returning user_id, email`, [token, `{${types.join(",")}}`, normalizedEmail, AuthHandler.MAX_OTP_ATTEMPTS]);
1659
- const row = res.rows[0];
1660
- if (!row) {
1661
- if (normalizedEmail) {
1662
- await this.db.query(`update auth.one_time_tokens set attempts = attempts + 1
1663
- where email = $1 and token_type = any($2::text[]) and expires_at > now()`, [normalizedEmail, `{${types.join(",")}}`]);
1664
- await this.db.query(`delete from auth.one_time_tokens where email = $1 and attempts >= $2`, [normalizedEmail, AuthHandler.MAX_OTP_ATTEMPTS]);
1665
- }
1657
+ const redemption = {
1658
+ token,
1659
+ tokenTypes: `{${types.join(",")}}`,
1660
+ email: email?.toLowerCase().trim() ?? null
1661
+ };
1662
+ return this.db.transaction((query) => this.claimEmailToken(query, redemption));
1663
+ }
1664
+ async claimEmailToken(query, redemption) {
1665
+ const candidate = await this.findEmailTokenCandidate(query, redemption);
1666
+ if (!candidate) {
1667
+ await this.recordFailedEmailTokenAttempt(query, redemption);
1666
1668
  return null;
1667
1669
  }
1668
- await this.db.query(`delete from auth.one_time_tokens where email = $1`, [row.email]);
1669
- const ures = await this.db.query(`update auth.users set email_confirmed_at = coalesce(email_confirmed_at, now()), last_sign_in_at = now()
1670
- where id = $1 returning *`, [row.user_id]);
1671
- return ures.rows[0] ?? null;
1670
+ const claimedToken = await this.lockAndDeleteEmailToken(query, candidate, redemption);
1671
+ return claimedToken ? this.confirmEmailTokenUser(query, claimedToken) : null;
1672
+ }
1673
+ async lockAndDeleteEmailToken(query, candidate, redemption) {
1674
+ await query(`select id from auth.users where id = $1 for update`, [candidate.user_id]);
1675
+ const claimedTokens = await query(`delete from auth.one_time_tokens
1676
+ where id = $1 and token = $2 and token_type = any($3::text[])
1677
+ and expires_at > now() and attempts < $4
1678
+ returning id, user_id, email`, [candidate.id, redemption.token, redemption.tokenTypes, AuthHandler.MAX_OTP_ATTEMPTS]);
1679
+ return claimedTokens.rows[0] ?? null;
1680
+ }
1681
+ async confirmEmailTokenUser(query, claimedToken) {
1682
+ await query(`delete from auth.one_time_tokens where email = $1`, [claimedToken.email]);
1683
+ const users = await query(`update auth.users set email_confirmed_at = coalesce(email_confirmed_at, now()), last_sign_in_at = now()
1684
+ where id = $1 returning *`, [claimedToken.user_id]);
1685
+ return users.rows[0] ?? null;
1686
+ }
1687
+ async findEmailTokenCandidate(query, redemption) {
1688
+ const candidates = await query(`select id, user_id, email from auth.one_time_tokens
1689
+ where token = $1 and token_type = any($2::text[])
1690
+ and ($3::text is null or email = $3) and expires_at > now()
1691
+ and attempts < $4`, [redemption.token, redemption.tokenTypes, redemption.email, AuthHandler.MAX_OTP_ATTEMPTS]);
1692
+ return candidates.rows[0] ?? null;
1693
+ }
1694
+ async recordFailedEmailTokenAttempt(query, redemption) {
1695
+ if (!redemption.email)
1696
+ return;
1697
+ await query(`update auth.one_time_tokens set attempts = attempts + 1
1698
+ where email = $1 and token_type = any($2::text[]) and expires_at > now()`, [redemption.email, redemption.tokenTypes]);
1699
+ await query(`delete from auth.one_time_tokens where email = $1 and attempts >= $2`, [redemption.email, AuthHandler.MAX_OTP_ATTEMPTS]);
1672
1700
  }
1673
1701
  async verifyToken(req) {
1674
1702
  const body = await req.json().catch(() => ({}));
1675
- if (!body.token)
1676
- return authError(400, "validation_failed", "token is required");
1703
+ const token = body.token ?? body.token_hash;
1704
+ if (!token)
1705
+ return authError(400, "validation_failed", "token or token_hash is required");
1677
1706
  if (body.type === "sms" || body.phone !== undefined) {
1678
1707
  const phone = normalizePhone(body.phone);
1679
- if (body.type !== "sms" || !phone || !/^\d{6,10}$/.test(body.token)) {
1708
+ if (body.type !== "sms" || !phone || !body.token || !/^\d{6,10}$/.test(body.token)) {
1680
1709
  return authError(400, "validation_failed", "phone, token, and type=sms are required");
1681
1710
  }
1682
1711
  const fingerprint = await keyedDigest(this.config.jwtSecret, "supacloud-lite:phone-fingerprint:v1", phone);
@@ -1695,7 +1724,7 @@ Or sign in with this link: ${link}`
1695
1724
  return json(200, session);
1696
1725
  }
1697
1726
  const types = body.type === "recovery" ? ["recovery"] : body.type === "magiclink" ? ["magiclink"] : ["otp", "magiclink"];
1698
- const user = await this.redeem(body.token, types, body.email);
1727
+ const user = await this.redeem(token, types, body.email);
1699
1728
  if (!user)
1700
1729
  return authError(403, "otp_expired", "Token has expired or is invalid");
1701
1730
  return json(200, await this.sessionFor(user));
@@ -1753,6 +1782,9 @@ Or sign in with this link: ${link}`
1753
1782
  }
1754
1783
  const idMatch = path.match(/^admin\/users\/([0-9a-f-]{36})$/);
1755
1784
  const exportMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/export$/);
1785
+ if (path === "admin/generate_link" && method === "POST") {
1786
+ return await this.generateAdminMagicLink(req);
1787
+ }
1756
1788
  if (path === "admin/audit" && method === "GET") {
1757
1789
  const res = await this.db.query(`select id, payload, created_at, ip_address from auth.audit_log_entries
1758
1790
  order by created_at desc limit 200`);
@@ -1823,6 +1855,50 @@ Or sign in with this link: ${link}`
1823
1855
  }
1824
1856
  return authError(404, "not_found", `unknown admin endpoint`);
1825
1857
  }
1858
+ async generateAdminMagicLink(req) {
1859
+ const body = await req.json().catch(() => ({}));
1860
+ if (body.type !== "magiclink") {
1861
+ return authError(422, "unsupported_link_type", "Only magiclink generation is supported");
1862
+ }
1863
+ if (!body.email)
1864
+ return authError(400, "validation_failed", "email is required");
1865
+ const email = body.email.toLowerCase().trim();
1866
+ const emailOtp = randomOtp(this.settings.otpLength);
1867
+ const hashedToken = await sha256Hex(randomToken(32));
1868
+ const expiry = `${this.settings.otpExpirySeconds} seconds`;
1869
+ const requestUrl = new URL(req.url);
1870
+ const redirectTo = resolveRedirect(requestUrl.searchParams.get("redirect_to") ?? body.redirect_to, this.config.siteUrl, this.config.uriAllowList, this.config.enforceRedirectAllowList);
1871
+ const user = await this.db.transaction(async (query) => {
1872
+ const result = await query(`insert into auth.users
1873
+ (aud, role, email, raw_app_meta_data, raw_user_meta_data)
1874
+ values ('authenticated', 'authenticated', $1, '{"provider":"email","providers":["email"]}', $2::jsonb)
1875
+ on conflict (email) do update set email = excluded.email
1876
+ returning *`, [email, JSON.stringify(body.data ?? {})]);
1877
+ const linkedUser = result.rows[0];
1878
+ await query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
1879
+ values ($1::uuid, 'email', $1::text, $2::jsonb)
1880
+ on conflict (provider, provider_id) do nothing`, [linkedUser.id, JSON.stringify({ sub: linkedUser.id, email })]);
1881
+ await query(`delete from auth.one_time_tokens
1882
+ where email = $1 and token_type = any($2::text[])`, [email, "{otp,magiclink}"]);
1883
+ await query(`insert into auth.one_time_tokens (user_id, email, token_type, token, expires_at)
1884
+ values ($1, $2, 'otp', $3, now() + $5::interval),
1885
+ ($1, $2, 'magiclink', $4, now() + $5::interval)`, [linkedUser.id, email, emailOtp, hashedToken, expiry]);
1886
+ return linkedUser;
1887
+ });
1888
+ const actionUrl = new URL(`${this.config.apiUrl}/auth/v1/verify`);
1889
+ actionUrl.searchParams.set("token", hashedToken);
1890
+ actionUrl.searchParams.set("type", "magiclink");
1891
+ actionUrl.searchParams.set("redirect_to", redirectTo);
1892
+ await this.audit("user_magiclink_requested", { actorId: user.id, actorEmail: email, type: "admin" });
1893
+ return json(200, {
1894
+ ...this.userJson(user, [], await this.getUserIdentities(user.id)),
1895
+ action_link: actionUrl.toString(),
1896
+ email_otp: emailOtp,
1897
+ hashed_token: hashedToken,
1898
+ redirect_to: redirectTo,
1899
+ verification_type: "magiclink"
1900
+ });
1901
+ }
1826
1902
  async audit(action, opts = {}) {
1827
1903
  try {
1828
1904
  const payload = {
@@ -4148,7 +4224,7 @@ class Database {
4148
4224
  }
4149
4225
  }
4150
4226
  if (seedSql) {
4151
- const hash = await sha256Hex(seedSql);
4227
+ const hash = await sha256Hex2(seedSql);
4152
4228
  const seen = await this.engine.query(`select 1 from supabase_migrations.seed_files where path = 'supabase/seed.sql' and hash = $1`, [hash]);
4153
4229
  if (seen.rows.length === 0) {
4154
4230
  await this.engine.transaction(async (tx) => {
@@ -4441,7 +4517,7 @@ function quoteIdent(name) {
4441
4517
  function quoteLiteral(value) {
4442
4518
  return `'${value.replaceAll("'", "''")}'`;
4443
4519
  }
4444
- async function sha256Hex(text) {
4520
+ async function sha256Hex2(text) {
4445
4521
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
4446
4522
  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
4447
4523
  }
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ function randomToken(bytes = 32) {
71
71
  // package.json
72
72
  var package_default = {
73
73
  name: "@supacloud/lite",
74
- version: "0.6.0",
74
+ version: "0.7.0",
75
75
  description: "Bun-native, single-project Supabase-compatible backend powered by PGlite",
76
76
  type: "module",
77
77
  license: "Apache-2.0",
@@ -1117,6 +1117,10 @@ function randomOtp(length) {
1117
1117
  }
1118
1118
  return code;
1119
1119
  }
1120
+ async function sha256Hex(value) {
1121
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
1122
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1123
+ }
1120
1124
  function normalizePhone(value) {
1121
1125
  if (typeof value !== "string")
1122
1126
  return null;
@@ -1655,33 +1659,58 @@ Or sign in with this link: ${link}`
1655
1659
  }
1656
1660
  static MAX_OTP_ATTEMPTS = 5;
1657
1661
  async redeem(token, types, email) {
1658
- const normalizedEmail = email?.toLowerCase().trim() ?? null;
1659
- const res = await this.db.query(`delete from auth.one_time_tokens
1660
- where token = $1 and token_type = any($2::text[])
1661
- and ($3::text is null or email = $3) and expires_at > now()
1662
- and attempts < $4
1663
- returning user_id, email`, [token, `{${types.join(",")}}`, normalizedEmail, AuthHandler.MAX_OTP_ATTEMPTS]);
1664
- const row = res.rows[0];
1665
- if (!row) {
1666
- if (normalizedEmail) {
1667
- await this.db.query(`update auth.one_time_tokens set attempts = attempts + 1
1668
- where email = $1 and token_type = any($2::text[]) and expires_at > now()`, [normalizedEmail, `{${types.join(",")}}`]);
1669
- await this.db.query(`delete from auth.one_time_tokens where email = $1 and attempts >= $2`, [normalizedEmail, AuthHandler.MAX_OTP_ATTEMPTS]);
1670
- }
1662
+ const redemption = {
1663
+ token,
1664
+ tokenTypes: `{${types.join(",")}}`,
1665
+ email: email?.toLowerCase().trim() ?? null
1666
+ };
1667
+ return this.db.transaction((query) => this.claimEmailToken(query, redemption));
1668
+ }
1669
+ async claimEmailToken(query, redemption) {
1670
+ const candidate = await this.findEmailTokenCandidate(query, redemption);
1671
+ if (!candidate) {
1672
+ await this.recordFailedEmailTokenAttempt(query, redemption);
1671
1673
  return null;
1672
1674
  }
1673
- await this.db.query(`delete from auth.one_time_tokens where email = $1`, [row.email]);
1674
- const ures = await this.db.query(`update auth.users set email_confirmed_at = coalesce(email_confirmed_at, now()), last_sign_in_at = now()
1675
- where id = $1 returning *`, [row.user_id]);
1676
- return ures.rows[0] ?? null;
1675
+ const claimedToken = await this.lockAndDeleteEmailToken(query, candidate, redemption);
1676
+ return claimedToken ? this.confirmEmailTokenUser(query, claimedToken) : null;
1677
+ }
1678
+ async lockAndDeleteEmailToken(query, candidate, redemption) {
1679
+ await query(`select id from auth.users where id = $1 for update`, [candidate.user_id]);
1680
+ const claimedTokens = await query(`delete from auth.one_time_tokens
1681
+ where id = $1 and token = $2 and token_type = any($3::text[])
1682
+ and expires_at > now() and attempts < $4
1683
+ returning id, user_id, email`, [candidate.id, redemption.token, redemption.tokenTypes, AuthHandler.MAX_OTP_ATTEMPTS]);
1684
+ return claimedTokens.rows[0] ?? null;
1685
+ }
1686
+ async confirmEmailTokenUser(query, claimedToken) {
1687
+ await query(`delete from auth.one_time_tokens where email = $1`, [claimedToken.email]);
1688
+ const users = await query(`update auth.users set email_confirmed_at = coalesce(email_confirmed_at, now()), last_sign_in_at = now()
1689
+ where id = $1 returning *`, [claimedToken.user_id]);
1690
+ return users.rows[0] ?? null;
1691
+ }
1692
+ async findEmailTokenCandidate(query, redemption) {
1693
+ const candidates = await query(`select id, user_id, email from auth.one_time_tokens
1694
+ where token = $1 and token_type = any($2::text[])
1695
+ and ($3::text is null or email = $3) and expires_at > now()
1696
+ and attempts < $4`, [redemption.token, redemption.tokenTypes, redemption.email, AuthHandler.MAX_OTP_ATTEMPTS]);
1697
+ return candidates.rows[0] ?? null;
1698
+ }
1699
+ async recordFailedEmailTokenAttempt(query, redemption) {
1700
+ if (!redemption.email)
1701
+ return;
1702
+ await query(`update auth.one_time_tokens set attempts = attempts + 1
1703
+ where email = $1 and token_type = any($2::text[]) and expires_at > now()`, [redemption.email, redemption.tokenTypes]);
1704
+ await query(`delete from auth.one_time_tokens where email = $1 and attempts >= $2`, [redemption.email, AuthHandler.MAX_OTP_ATTEMPTS]);
1677
1705
  }
1678
1706
  async verifyToken(req) {
1679
1707
  const body = await req.json().catch(() => ({}));
1680
- if (!body.token)
1681
- return authError(400, "validation_failed", "token is required");
1708
+ const token = body.token ?? body.token_hash;
1709
+ if (!token)
1710
+ return authError(400, "validation_failed", "token or token_hash is required");
1682
1711
  if (body.type === "sms" || body.phone !== undefined) {
1683
1712
  const phone = normalizePhone(body.phone);
1684
- if (body.type !== "sms" || !phone || !/^\d{6,10}$/.test(body.token)) {
1713
+ if (body.type !== "sms" || !phone || !body.token || !/^\d{6,10}$/.test(body.token)) {
1685
1714
  return authError(400, "validation_failed", "phone, token, and type=sms are required");
1686
1715
  }
1687
1716
  const fingerprint = await keyedDigest(this.config.jwtSecret, "supacloud-lite:phone-fingerprint:v1", phone);
@@ -1700,7 +1729,7 @@ Or sign in with this link: ${link}`
1700
1729
  return json(200, session);
1701
1730
  }
1702
1731
  const types = body.type === "recovery" ? ["recovery"] : body.type === "magiclink" ? ["magiclink"] : ["otp", "magiclink"];
1703
- const user = await this.redeem(body.token, types, body.email);
1732
+ const user = await this.redeem(token, types, body.email);
1704
1733
  if (!user)
1705
1734
  return authError(403, "otp_expired", "Token has expired or is invalid");
1706
1735
  return json(200, await this.sessionFor(user));
@@ -1758,6 +1787,9 @@ Or sign in with this link: ${link}`
1758
1787
  }
1759
1788
  const idMatch = path.match(/^admin\/users\/([0-9a-f-]{36})$/);
1760
1789
  const exportMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/export$/);
1790
+ if (path === "admin/generate_link" && method === "POST") {
1791
+ return await this.generateAdminMagicLink(req);
1792
+ }
1761
1793
  if (path === "admin/audit" && method === "GET") {
1762
1794
  const res = await this.db.query(`select id, payload, created_at, ip_address from auth.audit_log_entries
1763
1795
  order by created_at desc limit 200`);
@@ -1828,6 +1860,50 @@ Or sign in with this link: ${link}`
1828
1860
  }
1829
1861
  return authError(404, "not_found", `unknown admin endpoint`);
1830
1862
  }
1863
+ async generateAdminMagicLink(req) {
1864
+ const body = await req.json().catch(() => ({}));
1865
+ if (body.type !== "magiclink") {
1866
+ return authError(422, "unsupported_link_type", "Only magiclink generation is supported");
1867
+ }
1868
+ if (!body.email)
1869
+ return authError(400, "validation_failed", "email is required");
1870
+ const email = body.email.toLowerCase().trim();
1871
+ const emailOtp = randomOtp(this.settings.otpLength);
1872
+ const hashedToken = await sha256Hex(randomToken(32));
1873
+ const expiry = `${this.settings.otpExpirySeconds} seconds`;
1874
+ const requestUrl = new URL(req.url);
1875
+ const redirectTo = resolveRedirect(requestUrl.searchParams.get("redirect_to") ?? body.redirect_to, this.config.siteUrl, this.config.uriAllowList, this.config.enforceRedirectAllowList);
1876
+ const user = await this.db.transaction(async (query) => {
1877
+ const result = await query(`insert into auth.users
1878
+ (aud, role, email, raw_app_meta_data, raw_user_meta_data)
1879
+ values ('authenticated', 'authenticated', $1, '{"provider":"email","providers":["email"]}', $2::jsonb)
1880
+ on conflict (email) do update set email = excluded.email
1881
+ returning *`, [email, JSON.stringify(body.data ?? {})]);
1882
+ const linkedUser = result.rows[0];
1883
+ await query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
1884
+ values ($1::uuid, 'email', $1::text, $2::jsonb)
1885
+ on conflict (provider, provider_id) do nothing`, [linkedUser.id, JSON.stringify({ sub: linkedUser.id, email })]);
1886
+ await query(`delete from auth.one_time_tokens
1887
+ where email = $1 and token_type = any($2::text[])`, [email, "{otp,magiclink}"]);
1888
+ await query(`insert into auth.one_time_tokens (user_id, email, token_type, token, expires_at)
1889
+ values ($1, $2, 'otp', $3, now() + $5::interval),
1890
+ ($1, $2, 'magiclink', $4, now() + $5::interval)`, [linkedUser.id, email, emailOtp, hashedToken, expiry]);
1891
+ return linkedUser;
1892
+ });
1893
+ const actionUrl = new URL(`${this.config.apiUrl}/auth/v1/verify`);
1894
+ actionUrl.searchParams.set("token", hashedToken);
1895
+ actionUrl.searchParams.set("type", "magiclink");
1896
+ actionUrl.searchParams.set("redirect_to", redirectTo);
1897
+ await this.audit("user_magiclink_requested", { actorId: user.id, actorEmail: email, type: "admin" });
1898
+ return json(200, {
1899
+ ...this.userJson(user, [], await this.getUserIdentities(user.id)),
1900
+ action_link: actionUrl.toString(),
1901
+ email_otp: emailOtp,
1902
+ hashed_token: hashedToken,
1903
+ redirect_to: redirectTo,
1904
+ verification_type: "magiclink"
1905
+ });
1906
+ }
1831
1907
  async audit(action, opts = {}) {
1832
1908
  try {
1833
1909
  const payload = {
@@ -4153,7 +4229,7 @@ class Database {
4153
4229
  }
4154
4230
  }
4155
4231
  if (seedSql) {
4156
- const hash = await sha256Hex(seedSql);
4232
+ const hash = await sha256Hex2(seedSql);
4157
4233
  const seen = await this.engine.query(`select 1 from supabase_migrations.seed_files where path = 'supabase/seed.sql' and hash = $1`, [hash]);
4158
4234
  if (seen.rows.length === 0) {
4159
4235
  await this.engine.transaction(async (tx) => {
@@ -4446,7 +4522,7 @@ function quoteIdent(name) {
4446
4522
  function quoteLiteral(value) {
4447
4523
  return `'${value.replaceAll("'", "''")}'`;
4448
4524
  }
4449
- async function sha256Hex(text) {
4525
+ async function sha256Hex2(text) {
4450
4526
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
4451
4527
  return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
4452
4528
  }
@@ -119,10 +119,16 @@ export declare class AuthHandler {
119
119
  /** Max wrong guesses for a one-time code before its tokens are invalidated. */
120
120
  private static readonly MAX_OTP_ATTEMPTS;
121
121
  private redeem;
122
+ private claimEmailToken;
123
+ private lockAndDeleteEmailToken;
124
+ private confirmEmailTokenUser;
125
+ private findEmailTokenCandidate;
126
+ private recordFailedEmailTokenAttempt;
122
127
  private verifyToken;
123
128
  private redeemPhoneOtp;
124
129
  private verifyLink;
125
130
  private admin;
131
+ private generateAdminMagicLink;
126
132
  /**
127
133
  * Append a security event to auth.audit_log_entries (GoTrue-compatible
128
134
  * payload). Best-effort: a logging failure never breaks the request.
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/runtime/auth/handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAW,MAAM,mBAAmB,CAAA;AAE1D,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAc,SAAS,EAAE,MAAM,aAAa,CAAA;AAEhF,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAGnE,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAA;AAExE,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAG7C,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAA;IACd,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAA;IACf,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAA;IACjB,yGAAyG;IACzG,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,uEAAuE;IACvE,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,+DAA+D;IAC/D,SAAS,CAAC,EAAE,SAAS,GAAG,IAAI,CAAA;IAC5B,oFAAoF;IACpF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;IACpD,gFAAgF;IAChF,UAAU,CAAC,EAAE,OAAO,KAAK,CAAA;IACzB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC;;;OAGG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;CACjC;AAED,UAAU,OAAO;IACf,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,kBAAkB,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IACxC,eAAe,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IACrC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACjD,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAClD,UAAU,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAChC,UAAU,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAChC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,kBAAkB,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IACxC,YAAY,EAAE,OAAO,GAAG,IAAI,CAAA;IAC5B,YAAY,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAClC,UAAU,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;CACjC;AA+GD,wEAAwE;AACxE,qBAAa,WAAW;IAYpB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,MAAM;IAZhB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAS;IAC1D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAM;IACrD,OAAO,CAAC,KAAK,CAAc;IAC3B,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,eAAe,CAA2B;IAClD,OAAO,CAAC,wBAAwB,CAA6B;IAC7D,OAAO,CAAC,QAAQ,CAAQ;IAExB,YACU,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,UAAU,EAa3B;IAED;;;OAGG;IACH,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,QAAQ;IAUhB,4EAA4E;IACtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAM1B;YAEa,oCAAoC;IAgBlD,8FAA8F;IACxF,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuD3E;YAIa,MAAM;YAiEN,KAAK;YAwCL,OAAO;YAMP,UAAU;YAgEV,MAAM;YAwBN,UAAU;YA4CV,OAAO;YAqBP,eAAe;YAqDf,eAAe;YAmCf,SAAS;YAwBT,kBAAkB;IAShC,OAAO,CAAC,mBAAmB;YASb,eAAe;YAgBf,YAAY;YAaZ,qBAAqB;YAgBrB,qBAAqB;IAUnC,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,kBAAkB;YAQZ,YAAY;IAM1B,+EAA+E;IAC/E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAI;YAE9B,MAAM;YAqCN,WAAW;YA+BX,cAAc;YAyDd,UAAU;YAuBV,KAAK;IA2FnB;;;OAGG;YACW,KAAK;IAqBnB;;;;;OAKG;YACW,UAAU;IA2CxB;;;;;;;;;OASG;YACW,SAAS;YAqBT,YAAY;YAyDZ,eAAe;YAqBf,YAAY;YAyCZ,cAAc;YAWd,cAAc;IAmB5B,oFAAoF;YACtE,iBAAiB;YAuBjB,kBAAkB;YAqBlB,iBAAiB;YAUjB,uBAAuB;IASrC,OAAO,CAAC,kBAAkB;YAoBZ,cAAc;YAOd,gBAAgB;IAM9B,wEAAwE;IACxE,QAAQ,CACN,CAAC,EAAE,OAAO,EACV,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAO,EACvC,UAAU,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAO,GACzC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBzB;IAED,+EAA+E;YACjE,gBAAgB;YAUhB,UAAU;CAqDzB"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/runtime/auth/handler.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/lite",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Bun-native, single-project Supabase-compatible backend powered by PGlite",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",