@supacloud/lite 0.8.0 → 0.8.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 +14 -0
- package/README.md +14 -0
- package/dist/cli.js +141 -48
- package/dist/index.js +141 -48
- package/dist/runtime/auth/handler.d.ts +5 -2
- package/dist/runtime/auth/handler.d.ts.map +1 -1
- package/dist/runtime/node/native/engine.d.ts +8 -1
- package/dist/runtime/node/native/engine.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.2](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.8.1...supacloud-lite-v0.8.2) (2026-08-15)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **lite:** support mirrored native postgres downloads ([#916](https://github.com/zuohuadong/supacloud/issues/916)) ([48224b7](https://github.com/zuohuadong/supacloud/commit/48224b7b067fcc56b3be8059301525cebeefb98d))
|
|
9
|
+
|
|
10
|
+
## [0.8.1](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.8.0...supacloud-lite-v0.8.1) (2026-08-15)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **lite:** complete extended auth compatibility ([#912](https://github.com/zuohuadong/supacloud/issues/912)) ([dd657f5](https://github.com/zuohuadong/supacloud/commit/dd657f5b52f2f3d65c0178f76fd78d027c153c5a))
|
|
16
|
+
|
|
3
17
|
## [0.8.0](https://github.com/zuohuadong/supacloud/compare/supacloud-lite-v0.7.3...supacloud-lite-v0.8.0) (2026-08-15)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -135,9 +135,16 @@ supacloud-lite version
|
|
|
135
135
|
- `SUPACLOUD_LITE_STORAGE_DIR`
|
|
136
136
|
- `SUPACLOUD_LITE_STORAGE_BACKEND`:`fs`(默认)、`memory` 或 `s3`
|
|
137
137
|
- `SUPACLOUD_LITE_S3_PREFIX`:S3 对象 key 前缀,可被 `--s3-prefix` 覆盖
|
|
138
|
+
- `SUPACLOUD_LITE_POSTGRES_MIRROR`:native 引擎下载 PostgreSQL 发布包时使用的 HTTPS 镜像前缀,例如 `https://ghproxy.net/`
|
|
138
139
|
- `SUPACLOUD_LITE_JWT_SECRET`
|
|
139
140
|
- `SUPACLOUD_LITE_VAULT_KEY`
|
|
140
141
|
|
|
142
|
+
native 引擎首次运行会下载约 12 MB 的 Theseus PostgreSQL 发布包并缓存在 `~/.cache/supacloud-lite`。网络无法直连 GitHub 时,可设置镜像前缀;Lite 会把完整上游 URL 追加到该前缀,并在解压执行前继续校验固定或上游发布的 SHA-256。镜像不能包含 URL 凭据、query 或 fragment;除 loopback 测试地址外必须使用 HTTPS。
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
SUPACLOUD_LITE_POSTGRES_MIRROR=https://ghproxy.net/ supacloud-lite migrate --engine native
|
|
146
|
+
```
|
|
147
|
+
|
|
141
148
|
使用远端 S3 时,启动前设置 `SUPACLOUD_LITE_STORAGE_BACKEND=s3`,并按 Bun S3 约定提供 `S3_BUCKET`、`S3_ACCESS_KEY_ID`、`S3_SECRET_ACCESS_KEY`、`S3_ENDPOINT`、`S3_REGION` 等变量;也支持对应的 `AWS_*` 变量。CLI 不接受密钥参数,避免凭据出现在进程列表中。
|
|
142
149
|
|
|
143
150
|
S3 模式下 `db reset` 会被拒绝,因为 Lite 不能把数据库元数据清理与远端对象删除做成原子操作;需要先明确处理远端 bucket/prefix,再切回本地或内存存储执行重置。
|
|
@@ -469,9 +476,16 @@ Environment variables:
|
|
|
469
476
|
- `SUPACLOUD_LITE_STORAGE_DIR`
|
|
470
477
|
- `SUPACLOUD_LITE_STORAGE_BACKEND`: `fs` (default), `memory`, or `s3`
|
|
471
478
|
- `SUPACLOUD_LITE_S3_PREFIX`: S3 object key prefix, can be overridden by `--s3-prefix`
|
|
479
|
+
- `SUPACLOUD_LITE_POSTGRES_MIRROR`: HTTPS prefix used to proxy native-engine PostgreSQL release downloads, for example `https://ghproxy.net/`
|
|
472
480
|
- `SUPACLOUD_LITE_JWT_SECRET`
|
|
473
481
|
- `SUPACLOUD_LITE_VAULT_KEY`
|
|
474
482
|
|
|
483
|
+
On its first run, the native engine downloads an approximately 12 MB Theseus PostgreSQL release into `~/.cache/supacloud-lite`. If GitHub is not directly reachable, set a mirror prefix; Lite appends the complete upstream URL and still verifies the pinned or published SHA-256 before extracting or executing the archive. The mirror must not contain URL credentials, a query, or a fragment, and must use HTTPS except for loopback test endpoints.
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
SUPACLOUD_LITE_POSTGRES_MIRROR=https://ghproxy.net/ supacloud-lite migrate --engine native
|
|
487
|
+
```
|
|
488
|
+
|
|
475
489
|
When using remote S3, set `SUPACLOUD_LITE_STORAGE_BACKEND=s3` before starting, and provide variables such as `S3_BUCKET`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_ENDPOINT`, and `S3_REGION` according to Bun S3 conventions; the corresponding `AWS_*` variables are also supported. The CLI does not accept secret parameters to avoid credentials appearing in the process list.
|
|
476
490
|
|
|
477
491
|
In S3 mode, `db reset` is rejected because Lite cannot make database metadata cleanup and remote object deletion an atomic operation; you must explicitly handle the remote bucket/prefix first, then switch back to local or memory storage to perform the reset.
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { dirname as dirname7, join as join10, resolve as resolve4 } from "path";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "@supacloud/lite",
|
|
11
|
-
version: "0.8.
|
|
11
|
+
version: "0.8.2",
|
|
12
12
|
description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
|
|
13
13
|
type: "module",
|
|
14
14
|
license: "Apache-2.0",
|
|
@@ -1296,7 +1296,8 @@ class AuthHandler {
|
|
|
1296
1296
|
return await this.oauth.authorize(url);
|
|
1297
1297
|
}
|
|
1298
1298
|
if (path === "callback" && (method === "GET" || method === "POST")) {
|
|
1299
|
-
|
|
1299
|
+
const callbackUrl = method === "POST" ? await this.oauthCallbackUrl(req, url) : url;
|
|
1300
|
+
return await this.oauth.callback(callbackUrl, (userId) => this.oauthSessionTokensFor(userId));
|
|
1300
1301
|
}
|
|
1301
1302
|
if (path.startsWith("admin/"))
|
|
1302
1303
|
return await this.admin(req, ctx, path, method);
|
|
@@ -1306,6 +1307,16 @@ class AuthHandler {
|
|
|
1306
1307
|
return authError(500, "unexpected_failure", msg);
|
|
1307
1308
|
}
|
|
1308
1309
|
}
|
|
1310
|
+
async oauthCallbackUrl(req, url) {
|
|
1311
|
+
const callbackUrl = new URL(url);
|
|
1312
|
+
const form = await req.formData();
|
|
1313
|
+
for (const field of ["code", "state"]) {
|
|
1314
|
+
const value = form.get(field);
|
|
1315
|
+
if (typeof value === "string" && value)
|
|
1316
|
+
callbackUrl.searchParams.set(field, value);
|
|
1317
|
+
}
|
|
1318
|
+
return callbackUrl;
|
|
1319
|
+
}
|
|
1309
1320
|
async signup(req) {
|
|
1310
1321
|
const body = await req.json().catch(() => ({}));
|
|
1311
1322
|
if (!body.email && !body.password) {
|
|
@@ -1383,7 +1394,7 @@ class AuthHandler {
|
|
|
1383
1394
|
if (!userId)
|
|
1384
1395
|
return authError(403, "flow_state_not_found", "invalid or expired auth code");
|
|
1385
1396
|
const ures = await this.db.query(`select * from auth.users where id = $1`, [userId]);
|
|
1386
|
-
return json(200, await this.
|
|
1397
|
+
return json(200, await this.sessionForOAuth(ures.rows[0]));
|
|
1387
1398
|
}
|
|
1388
1399
|
return authError(400, "invalid_grant", `unsupported grant_type: ${grantType}`);
|
|
1389
1400
|
}
|
|
@@ -1428,17 +1439,18 @@ class AuthHandler {
|
|
|
1428
1439
|
sets.push(`is_anonymous = false`);
|
|
1429
1440
|
sets.push(`raw_app_meta_data = coalesce(raw_app_meta_data, '{}'::jsonb) || '{"provider":"email","providers":["email"]}'::jsonb`);
|
|
1430
1441
|
}
|
|
1431
|
-
if (sets.length === 0)
|
|
1432
|
-
return json(200, this.userJson(user));
|
|
1433
|
-
params.push(user.id);
|
|
1434
|
-
const res = await this.db.query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
|
|
1435
|
-
const updated = res.rows[0];
|
|
1436
|
-
if (upgradingAnon) {
|
|
1437
|
-
await this.db.query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
|
|
1438
|
-
values ($1, 'email', $2, $3)
|
|
1439
|
-
on conflict (provider, provider_id) do nothing`, [updated.id, updated.id, JSON.stringify({ sub: updated.id, email: updated.email })]);
|
|
1442
|
+
if (sets.length === 0) {
|
|
1443
|
+
return json(200, this.userJson(user, await this.getUserFactors(user.id), await this.getUserIdentities(user.id)));
|
|
1440
1444
|
}
|
|
1441
|
-
|
|
1445
|
+
params.push(user.id);
|
|
1446
|
+
const payload = await this.db.transaction(async (query) => {
|
|
1447
|
+
const res = await query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
|
|
1448
|
+
const updated = res.rows[0];
|
|
1449
|
+
if (body.email)
|
|
1450
|
+
await this.ensureEmailIdentity(updated, query);
|
|
1451
|
+
return this.userJsonWithRelations(updated, query);
|
|
1452
|
+
});
|
|
1453
|
+
return json(200, payload);
|
|
1442
1454
|
}
|
|
1443
1455
|
async logout(req, url) {
|
|
1444
1456
|
const claims = await this.claimsFromBearer(req);
|
|
@@ -1468,7 +1480,7 @@ class AuthHandler {
|
|
|
1468
1480
|
let user = res.rows[0];
|
|
1469
1481
|
if (!user) {
|
|
1470
1482
|
if (!createUser)
|
|
1471
|
-
return
|
|
1483
|
+
return json(200, {});
|
|
1472
1484
|
if (this.settings.disableSignup)
|
|
1473
1485
|
return authError(422, "signup_disabled", "Signups not allowed for this instance");
|
|
1474
1486
|
res = await this.db.query(`insert into auth.users (aud, role, email, raw_app_meta_data, raw_user_meta_data)
|
|
@@ -1800,6 +1812,8 @@ Or sign in with this link: ${link}`
|
|
|
1800
1812
|
}
|
|
1801
1813
|
const idMatch = path.match(/^admin\/users\/([0-9a-f-]{36})$/);
|
|
1802
1814
|
const exportMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/export$/);
|
|
1815
|
+
const factorsMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/factors$/);
|
|
1816
|
+
const factorMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/factors\/([0-9a-f-]{36})$/);
|
|
1803
1817
|
if (path === "admin/generate_link" && method === "POST") {
|
|
1804
1818
|
return await this.generateAdminMagicLink(req);
|
|
1805
1819
|
}
|
|
@@ -1811,32 +1825,49 @@ Or sign in with this link: ${link}`
|
|
|
1811
1825
|
if (exportMatch && method === "GET") {
|
|
1812
1826
|
return await this.exportUser(exportMatch[1]);
|
|
1813
1827
|
}
|
|
1828
|
+
if (factorsMatch && method === "GET") {
|
|
1829
|
+
const user = await this.db.query(`select 1 from auth.users where id = $1`, [factorsMatch[1]]);
|
|
1830
|
+
if (user.rows.length === 0)
|
|
1831
|
+
return authError(404, "user_not_found", "User not found");
|
|
1832
|
+
return json(200, await this.getUserFactors(factorsMatch[1]));
|
|
1833
|
+
}
|
|
1834
|
+
if (factorMatch && method === "DELETE") {
|
|
1835
|
+
const deleted = await this.db.query(`delete from auth.mfa_factors where user_id = $1 and id = $2 returning id`, [factorMatch[1], factorMatch[2]]);
|
|
1836
|
+
if (deleted.rows.length === 0)
|
|
1837
|
+
return authError(404, "mfa_factor_not_found", "MFA factor not found");
|
|
1838
|
+
return json(200, { id: factorMatch[2] });
|
|
1839
|
+
}
|
|
1814
1840
|
if (path === "admin/users" && method === "GET") {
|
|
1815
1841
|
const res = await this.db.query(`select * from auth.users order by created_at desc limit 1000`);
|
|
1816
|
-
return json(200, { users: res.rows.map((
|
|
1842
|
+
return json(200, { users: res.rows.map((user) => this.userJson(user)), aud: "authenticated" });
|
|
1817
1843
|
}
|
|
1818
1844
|
if (path === "admin/users" && method === "POST") {
|
|
1819
1845
|
const body = await req.json().catch(() => ({}));
|
|
1820
1846
|
if (!body.email)
|
|
1821
1847
|
return authError(400, "validation_failed", "email is required");
|
|
1822
1848
|
const hashed = body.password ? await hashPassword(body.password) : null;
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1849
|
+
const created = await this.db.transaction(async (query) => {
|
|
1850
|
+
const res = await query(`insert into auth.users
|
|
1851
|
+
(aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data)
|
|
1852
|
+
values ('authenticated', 'authenticated', $1, $2, case when $3 then now() else null end, $4, $5)
|
|
1853
|
+
returning *`, [
|
|
1854
|
+
body.email.toLowerCase().trim(),
|
|
1855
|
+
hashed,
|
|
1856
|
+
body.email_confirm ?? true,
|
|
1857
|
+
JSON.stringify({ provider: "email", providers: ["email"], ...body.app_metadata ?? {} }),
|
|
1858
|
+
JSON.stringify(body.user_metadata ?? {})
|
|
1859
|
+
]);
|
|
1860
|
+
const user = res.rows[0];
|
|
1861
|
+
await this.ensureEmailIdentity(user, query);
|
|
1862
|
+
return this.userJsonWithRelations(user, query);
|
|
1863
|
+
});
|
|
1864
|
+
return json(200, created);
|
|
1834
1865
|
}
|
|
1835
1866
|
if (idMatch && method === "GET") {
|
|
1836
1867
|
const res = await this.db.query(`select * from auth.users where id = $1`, [idMatch[1]]);
|
|
1837
1868
|
if (res.rows.length === 0)
|
|
1838
1869
|
return authError(404, "user_not_found", "User not found");
|
|
1839
|
-
return json(200, this.
|
|
1870
|
+
return json(200, await this.userJsonWithRelations(res.rows[0]));
|
|
1840
1871
|
}
|
|
1841
1872
|
if (idMatch && method === "PUT") {
|
|
1842
1873
|
const body = await req.json().catch(() => ({}));
|
|
@@ -1863,10 +1894,18 @@ Or sign in with this link: ${link}`
|
|
|
1863
1894
|
if (sets.length === 0)
|
|
1864
1895
|
return authError(400, "validation_failed", "nothing to update");
|
|
1865
1896
|
params.push(idMatch[1]);
|
|
1866
|
-
const
|
|
1867
|
-
|
|
1897
|
+
const updated = await this.db.transaction(async (query) => {
|
|
1898
|
+
const res = await query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
|
|
1899
|
+
const user = res.rows[0];
|
|
1900
|
+
if (!user)
|
|
1901
|
+
return null;
|
|
1902
|
+
if (typeof body.email === "string")
|
|
1903
|
+
await this.ensureEmailIdentity(user, query);
|
|
1904
|
+
return this.userJsonWithRelations(user, query);
|
|
1905
|
+
});
|
|
1906
|
+
if (!updated)
|
|
1868
1907
|
return authError(404, "user_not_found", "User not found");
|
|
1869
|
-
return json(200,
|
|
1908
|
+
return json(200, updated);
|
|
1870
1909
|
}
|
|
1871
1910
|
if (idMatch && method === "DELETE") {
|
|
1872
1911
|
return await this.eraseUser(idMatch[1]);
|
|
@@ -2062,17 +2101,26 @@ Or sign in with this link: ${link}`
|
|
|
2062
2101
|
if (!await verifyTotp(factor.secret, body.code ?? "")) {
|
|
2063
2102
|
return authError(422, "mfa_verification_failed", "Invalid TOTP code entered");
|
|
2064
2103
|
}
|
|
2065
|
-
await this.db.
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2104
|
+
const session = await this.db.transaction(async (query) => {
|
|
2105
|
+
const claimed = await query(`update auth.mfa_challenges set verified_at = now()
|
|
2106
|
+
where id = $1 and factor_id = $2 and verified_at is null and expires_at >= now()
|
|
2107
|
+
returning id`, [challenge.id, factorId]);
|
|
2108
|
+
if (claimed.rows.length === 0)
|
|
2109
|
+
return null;
|
|
2110
|
+
if (factor.status !== "verified") {
|
|
2111
|
+
await query(`update auth.mfa_factors set status = 'verified', updated_at = now() where id = $1`, [factorId]);
|
|
2112
|
+
}
|
|
2113
|
+
return this.sessionFor(user, undefined, {
|
|
2114
|
+
aal: "aal2",
|
|
2115
|
+
amr: [
|
|
2116
|
+
{ method: "password", timestamp: Math.floor(Date.now() / 1000) },
|
|
2117
|
+
{ method: "totp", timestamp: Math.floor(Date.now() / 1000) }
|
|
2118
|
+
]
|
|
2119
|
+
}, query);
|
|
2075
2120
|
});
|
|
2121
|
+
if (!session) {
|
|
2122
|
+
return authError(422, "mfa_verification_failed", "This challenge has already been verified");
|
|
2123
|
+
}
|
|
2076
2124
|
return json(200, session);
|
|
2077
2125
|
}
|
|
2078
2126
|
async unenrollFactor(req, factorId) {
|
|
@@ -2110,6 +2158,17 @@ Or sign in with this link: ${link}`
|
|
|
2110
2158
|
updated_at: iso(r.updated_at)
|
|
2111
2159
|
}));
|
|
2112
2160
|
}
|
|
2161
|
+
async ensureEmailIdentity(user, query = (sql, params) => this.db.query(sql, params)) {
|
|
2162
|
+
if (!user.email)
|
|
2163
|
+
return;
|
|
2164
|
+
await query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
|
|
2165
|
+
values ($1::uuid, 'email', $1::text, $2::jsonb)
|
|
2166
|
+
on conflict (provider, provider_id) do update
|
|
2167
|
+
set identity_data = excluded.identity_data, updated_at = now()`, [user.id, JSON.stringify({ sub: user.id, email: user.email })]);
|
|
2168
|
+
}
|
|
2169
|
+
async userJsonWithRelations(user, query = (sql, params) => this.db.query(sql, params)) {
|
|
2170
|
+
return this.userJson(user, await this.getUserFactors(user.id, query), await this.getUserIdentities(user.id, query));
|
|
2171
|
+
}
|
|
2113
2172
|
async rotateRefreshToken(token) {
|
|
2114
2173
|
return this.db.transaction(async (query) => {
|
|
2115
2174
|
const claimedToken = await this.claimRefreshToken(query, token);
|
|
@@ -2183,9 +2242,14 @@ Or sign in with this link: ${link}`
|
|
|
2183
2242
|
is_anonymous: u.is_anonymous ?? false
|
|
2184
2243
|
};
|
|
2185
2244
|
}
|
|
2186
|
-
|
|
2245
|
+
sessionForOAuth(user) {
|
|
2246
|
+
return this.sessionFor(user, undefined, {
|
|
2247
|
+
amr: [{ method: "oauth", timestamp: Math.floor(Date.now() / 1000) }]
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
async oauthSessionTokensFor(userId) {
|
|
2187
2251
|
const res = await this.db.query(`select * from auth.users where id = $1`, [userId]);
|
|
2188
|
-
const session = await this.
|
|
2252
|
+
const session = await this.sessionForOAuth(res.rows[0]);
|
|
2189
2253
|
return { access_token: session.access_token, refresh_token: session.refresh_token, expires_in: session.expires_in };
|
|
2190
2254
|
}
|
|
2191
2255
|
async sessionFor(user, parentToken, opts, query = (sql, params) => this.db.query(sql, params)) {
|
|
@@ -10732,6 +10796,7 @@ function toPgArrayLiteral(array) {
|
|
|
10732
10796
|
|
|
10733
10797
|
// src/runtime/node/native/engine.ts
|
|
10734
10798
|
var DEFAULT_PG_VERSION = "17.7.0";
|
|
10799
|
+
var POSTGRES_MIRROR_URL_ERROR = "SUPACLOUD_LITE_POSTGRES_MIRROR must be an absolute HTTPS URL or a loopback HTTP URL";
|
|
10735
10800
|
var NATIVE_POSTGRES_MAJOR = DEFAULT_PG_VERSION.split(".")[0];
|
|
10736
10801
|
function isNativeEngineSupported() {
|
|
10737
10802
|
return (process.platform === "darwin" || process.platform === "linux") && (process.arch === "arm64" || process.arch === "x64") && (process.platform !== "linux" || isGlibcLinux());
|
|
@@ -10763,7 +10828,7 @@ var PINNED_SHA256 = {
|
|
|
10763
10828
|
"postgresql-17.7.0-x86_64-apple-darwin": "0dd8c25173524bad4ae8ef6b970da1ac40f4c1f231150c416ccb8cd06feff8f2",
|
|
10764
10829
|
"postgresql-17.7.0-aarch64-apple-darwin": "727ac08d20a704014a0d51eb3300aa0c8e292c1cf0a1c99d4f4b1002e1420220"
|
|
10765
10830
|
};
|
|
10766
|
-
async function verifyTarball(tarball, key,
|
|
10831
|
+
async function verifyTarball(tarball, key, upstreamUrl, downloadMirror) {
|
|
10767
10832
|
const actual = createHash2("sha256").update(readFileSync(tarball)).digest("hex");
|
|
10768
10833
|
const pinned = PINNED_SHA256[key];
|
|
10769
10834
|
if (pinned) {
|
|
@@ -10772,7 +10837,8 @@ async function verifyTarball(tarball, key, url) {
|
|
|
10772
10837
|
}
|
|
10773
10838
|
return;
|
|
10774
10839
|
}
|
|
10775
|
-
const
|
|
10840
|
+
const checksumUrl = resolvePostgresDownloadUrl(`${upstreamUrl}.sha256`, downloadMirror);
|
|
10841
|
+
const res = await fetchRelease(checksumUrl);
|
|
10776
10842
|
if (!res.ok)
|
|
10777
10843
|
throw new Error(`could not fetch checksum for ${key}: HTTP ${res.status}`);
|
|
10778
10844
|
const expected = (await res.text()).trim().split(/\s+/)[0].toLowerCase();
|
|
@@ -10782,13 +10848,14 @@ async function verifyTarball(tarball, key, url) {
|
|
|
10782
10848
|
throw new Error(`postgres binary checksum mismatch for ${key}: expected ${expected}, got ${actual}`);
|
|
10783
10849
|
}
|
|
10784
10850
|
}
|
|
10785
|
-
async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
|
|
10851
|
+
async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log, downloadMirror) {
|
|
10786
10852
|
const t = target();
|
|
10787
10853
|
const root = cacheDir ?? join(homedir(), ".cache", "supacloud-lite");
|
|
10788
10854
|
const dir = join(root, `postgresql-${version}-${t}`);
|
|
10789
10855
|
if (isCompleteInstall(dir))
|
|
10790
10856
|
return dir;
|
|
10791
|
-
const
|
|
10857
|
+
const upstreamUrl = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
|
|
10858
|
+
const url = resolvePostgresDownloadUrl(upstreamUrl, downloadMirror);
|
|
10792
10859
|
mkdirSync(root, { recursive: true });
|
|
10793
10860
|
const uniq = `${process.pid}-${randomBytes2(6).toString("hex")}`;
|
|
10794
10861
|
const tarball = join(root, `pg-${version}-${uniq}.tar.gz`);
|
|
@@ -10801,7 +10868,7 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
|
|
|
10801
10868
|
if (!res.ok)
|
|
10802
10869
|
throw new Error(`failed to download ${url}: HTTP ${res.status}`);
|
|
10803
10870
|
await writeFile(tarball, Buffer.from(await res.arrayBuffer()));
|
|
10804
|
-
await verifyTarball(tarball, `postgresql-${version}-${t}`,
|
|
10871
|
+
await verifyTarball(tarball, `postgresql-${version}-${t}`, upstreamUrl, downloadMirror);
|
|
10805
10872
|
mkdirSync(tmpDir, { recursive: true });
|
|
10806
10873
|
await extractTar({ cwd: tmpDir, file: tarball, gzip: true, preserveOwner: false, strict: true, strip: 1 });
|
|
10807
10874
|
if (!isCompleteInstall(tmpDir))
|
|
@@ -10821,6 +10888,32 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
|
|
|
10821
10888
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
10822
10889
|
}
|
|
10823
10890
|
}
|
|
10891
|
+
function resolvePostgresDownloadUrl(upstreamUrl, downloadMirror) {
|
|
10892
|
+
const mirror = postgresDownloadMirror(downloadMirror);
|
|
10893
|
+
return mirror ? `${mirror.toString().replace(/\/+$/, "")}/${upstreamUrl}` : upstreamUrl;
|
|
10894
|
+
}
|
|
10895
|
+
function postgresDownloadMirror(downloadMirror) {
|
|
10896
|
+
const configuredMirror = downloadMirror ?? process.env.SUPACLOUD_LITE_POSTGRES_MIRROR;
|
|
10897
|
+
const mirrorText = configuredMirror?.trim();
|
|
10898
|
+
if (!mirrorText)
|
|
10899
|
+
return;
|
|
10900
|
+
let mirror;
|
|
10901
|
+
try {
|
|
10902
|
+
mirror = new URL(mirrorText);
|
|
10903
|
+
} catch {
|
|
10904
|
+
throw new Error(POSTGRES_MIRROR_URL_ERROR);
|
|
10905
|
+
}
|
|
10906
|
+
const loopbackHttp = mirror.protocol === "http:" && (mirror.hostname === "127.0.0.1" || mirror.hostname === "localhost" || mirror.hostname === "[::1]");
|
|
10907
|
+
if (mirror.protocol !== "https:" && !loopbackHttp)
|
|
10908
|
+
throw new Error(POSTGRES_MIRROR_URL_ERROR);
|
|
10909
|
+
if (mirror.username || mirror.password) {
|
|
10910
|
+
throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain credentials");
|
|
10911
|
+
}
|
|
10912
|
+
if (mirrorText.includes("?") || mirrorText.includes("#")) {
|
|
10913
|
+
throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain a query or fragment");
|
|
10914
|
+
}
|
|
10915
|
+
return mirror;
|
|
10916
|
+
}
|
|
10824
10917
|
async function fetchRelease(url) {
|
|
10825
10918
|
let lastError;
|
|
10826
10919
|
for (let attempt = 1;attempt <= 3; attempt++) {
|
|
@@ -10853,7 +10946,7 @@ async function createNativeEngine(opts) {
|
|
|
10853
10946
|
let postgres;
|
|
10854
10947
|
let removeExitHandler;
|
|
10855
10948
|
try {
|
|
10856
|
-
const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log);
|
|
10949
|
+
const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log, opts.downloadMirror);
|
|
10857
10950
|
const bin = (name) => join(installDir, "bin", name);
|
|
10858
10951
|
if (!existsSync(join(opts.dataDir, "PG_VERSION"))) {
|
|
10859
10952
|
mkdirSync(opts.dataDir, { recursive: true });
|
package/dist/index.js
CHANGED
|
@@ -85,7 +85,7 @@ function randomToken(bytes = 32) {
|
|
|
85
85
|
// package.json
|
|
86
86
|
var package_default = {
|
|
87
87
|
name: "@supacloud/lite",
|
|
88
|
-
version: "0.8.
|
|
88
|
+
version: "0.8.2",
|
|
89
89
|
description: "Bun-native, single-project Supabase-compatible backend powered by PGlite or native PostgreSQL",
|
|
90
90
|
type: "module",
|
|
91
91
|
license: "Apache-2.0",
|
|
@@ -1301,7 +1301,8 @@ class AuthHandler {
|
|
|
1301
1301
|
return await this.oauth.authorize(url);
|
|
1302
1302
|
}
|
|
1303
1303
|
if (path === "callback" && (method === "GET" || method === "POST")) {
|
|
1304
|
-
|
|
1304
|
+
const callbackUrl = method === "POST" ? await this.oauthCallbackUrl(req, url) : url;
|
|
1305
|
+
return await this.oauth.callback(callbackUrl, (userId) => this.oauthSessionTokensFor(userId));
|
|
1305
1306
|
}
|
|
1306
1307
|
if (path.startsWith("admin/"))
|
|
1307
1308
|
return await this.admin(req, ctx, path, method);
|
|
@@ -1311,6 +1312,16 @@ class AuthHandler {
|
|
|
1311
1312
|
return authError(500, "unexpected_failure", msg);
|
|
1312
1313
|
}
|
|
1313
1314
|
}
|
|
1315
|
+
async oauthCallbackUrl(req, url) {
|
|
1316
|
+
const callbackUrl = new URL(url);
|
|
1317
|
+
const form = await req.formData();
|
|
1318
|
+
for (const field of ["code", "state"]) {
|
|
1319
|
+
const value = form.get(field);
|
|
1320
|
+
if (typeof value === "string" && value)
|
|
1321
|
+
callbackUrl.searchParams.set(field, value);
|
|
1322
|
+
}
|
|
1323
|
+
return callbackUrl;
|
|
1324
|
+
}
|
|
1314
1325
|
async signup(req) {
|
|
1315
1326
|
const body = await req.json().catch(() => ({}));
|
|
1316
1327
|
if (!body.email && !body.password) {
|
|
@@ -1388,7 +1399,7 @@ class AuthHandler {
|
|
|
1388
1399
|
if (!userId)
|
|
1389
1400
|
return authError(403, "flow_state_not_found", "invalid or expired auth code");
|
|
1390
1401
|
const ures = await this.db.query(`select * from auth.users where id = $1`, [userId]);
|
|
1391
|
-
return json(200, await this.
|
|
1402
|
+
return json(200, await this.sessionForOAuth(ures.rows[0]));
|
|
1392
1403
|
}
|
|
1393
1404
|
return authError(400, "invalid_grant", `unsupported grant_type: ${grantType}`);
|
|
1394
1405
|
}
|
|
@@ -1433,17 +1444,18 @@ class AuthHandler {
|
|
|
1433
1444
|
sets.push(`is_anonymous = false`);
|
|
1434
1445
|
sets.push(`raw_app_meta_data = coalesce(raw_app_meta_data, '{}'::jsonb) || '{"provider":"email","providers":["email"]}'::jsonb`);
|
|
1435
1446
|
}
|
|
1436
|
-
if (sets.length === 0)
|
|
1437
|
-
return json(200, this.userJson(user));
|
|
1438
|
-
params.push(user.id);
|
|
1439
|
-
const res = await this.db.query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
|
|
1440
|
-
const updated = res.rows[0];
|
|
1441
|
-
if (upgradingAnon) {
|
|
1442
|
-
await this.db.query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
|
|
1443
|
-
values ($1, 'email', $2, $3)
|
|
1444
|
-
on conflict (provider, provider_id) do nothing`, [updated.id, updated.id, JSON.stringify({ sub: updated.id, email: updated.email })]);
|
|
1447
|
+
if (sets.length === 0) {
|
|
1448
|
+
return json(200, this.userJson(user, await this.getUserFactors(user.id), await this.getUserIdentities(user.id)));
|
|
1445
1449
|
}
|
|
1446
|
-
|
|
1450
|
+
params.push(user.id);
|
|
1451
|
+
const payload = await this.db.transaction(async (query) => {
|
|
1452
|
+
const res = await query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
|
|
1453
|
+
const updated = res.rows[0];
|
|
1454
|
+
if (body.email)
|
|
1455
|
+
await this.ensureEmailIdentity(updated, query);
|
|
1456
|
+
return this.userJsonWithRelations(updated, query);
|
|
1457
|
+
});
|
|
1458
|
+
return json(200, payload);
|
|
1447
1459
|
}
|
|
1448
1460
|
async logout(req, url) {
|
|
1449
1461
|
const claims = await this.claimsFromBearer(req);
|
|
@@ -1473,7 +1485,7 @@ class AuthHandler {
|
|
|
1473
1485
|
let user = res.rows[0];
|
|
1474
1486
|
if (!user) {
|
|
1475
1487
|
if (!createUser)
|
|
1476
|
-
return
|
|
1488
|
+
return json(200, {});
|
|
1477
1489
|
if (this.settings.disableSignup)
|
|
1478
1490
|
return authError(422, "signup_disabled", "Signups not allowed for this instance");
|
|
1479
1491
|
res = await this.db.query(`insert into auth.users (aud, role, email, raw_app_meta_data, raw_user_meta_data)
|
|
@@ -1805,6 +1817,8 @@ Or sign in with this link: ${link}`
|
|
|
1805
1817
|
}
|
|
1806
1818
|
const idMatch = path.match(/^admin\/users\/([0-9a-f-]{36})$/);
|
|
1807
1819
|
const exportMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/export$/);
|
|
1820
|
+
const factorsMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/factors$/);
|
|
1821
|
+
const factorMatch = path.match(/^admin\/users\/([0-9a-f-]{36})\/factors\/([0-9a-f-]{36})$/);
|
|
1808
1822
|
if (path === "admin/generate_link" && method === "POST") {
|
|
1809
1823
|
return await this.generateAdminMagicLink(req);
|
|
1810
1824
|
}
|
|
@@ -1816,32 +1830,49 @@ Or sign in with this link: ${link}`
|
|
|
1816
1830
|
if (exportMatch && method === "GET") {
|
|
1817
1831
|
return await this.exportUser(exportMatch[1]);
|
|
1818
1832
|
}
|
|
1833
|
+
if (factorsMatch && method === "GET") {
|
|
1834
|
+
const user = await this.db.query(`select 1 from auth.users where id = $1`, [factorsMatch[1]]);
|
|
1835
|
+
if (user.rows.length === 0)
|
|
1836
|
+
return authError(404, "user_not_found", "User not found");
|
|
1837
|
+
return json(200, await this.getUserFactors(factorsMatch[1]));
|
|
1838
|
+
}
|
|
1839
|
+
if (factorMatch && method === "DELETE") {
|
|
1840
|
+
const deleted = await this.db.query(`delete from auth.mfa_factors where user_id = $1 and id = $2 returning id`, [factorMatch[1], factorMatch[2]]);
|
|
1841
|
+
if (deleted.rows.length === 0)
|
|
1842
|
+
return authError(404, "mfa_factor_not_found", "MFA factor not found");
|
|
1843
|
+
return json(200, { id: factorMatch[2] });
|
|
1844
|
+
}
|
|
1819
1845
|
if (path === "admin/users" && method === "GET") {
|
|
1820
1846
|
const res = await this.db.query(`select * from auth.users order by created_at desc limit 1000`);
|
|
1821
|
-
return json(200, { users: res.rows.map((
|
|
1847
|
+
return json(200, { users: res.rows.map((user) => this.userJson(user)), aud: "authenticated" });
|
|
1822
1848
|
}
|
|
1823
1849
|
if (path === "admin/users" && method === "POST") {
|
|
1824
1850
|
const body = await req.json().catch(() => ({}));
|
|
1825
1851
|
if (!body.email)
|
|
1826
1852
|
return authError(400, "validation_failed", "email is required");
|
|
1827
1853
|
const hashed = body.password ? await hashPassword(body.password) : null;
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1854
|
+
const created = await this.db.transaction(async (query) => {
|
|
1855
|
+
const res = await query(`insert into auth.users
|
|
1856
|
+
(aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data)
|
|
1857
|
+
values ('authenticated', 'authenticated', $1, $2, case when $3 then now() else null end, $4, $5)
|
|
1858
|
+
returning *`, [
|
|
1859
|
+
body.email.toLowerCase().trim(),
|
|
1860
|
+
hashed,
|
|
1861
|
+
body.email_confirm ?? true,
|
|
1862
|
+
JSON.stringify({ provider: "email", providers: ["email"], ...body.app_metadata ?? {} }),
|
|
1863
|
+
JSON.stringify(body.user_metadata ?? {})
|
|
1864
|
+
]);
|
|
1865
|
+
const user = res.rows[0];
|
|
1866
|
+
await this.ensureEmailIdentity(user, query);
|
|
1867
|
+
return this.userJsonWithRelations(user, query);
|
|
1868
|
+
});
|
|
1869
|
+
return json(200, created);
|
|
1839
1870
|
}
|
|
1840
1871
|
if (idMatch && method === "GET") {
|
|
1841
1872
|
const res = await this.db.query(`select * from auth.users where id = $1`, [idMatch[1]]);
|
|
1842
1873
|
if (res.rows.length === 0)
|
|
1843
1874
|
return authError(404, "user_not_found", "User not found");
|
|
1844
|
-
return json(200, this.
|
|
1875
|
+
return json(200, await this.userJsonWithRelations(res.rows[0]));
|
|
1845
1876
|
}
|
|
1846
1877
|
if (idMatch && method === "PUT") {
|
|
1847
1878
|
const body = await req.json().catch(() => ({}));
|
|
@@ -1868,10 +1899,18 @@ Or sign in with this link: ${link}`
|
|
|
1868
1899
|
if (sets.length === 0)
|
|
1869
1900
|
return authError(400, "validation_failed", "nothing to update");
|
|
1870
1901
|
params.push(idMatch[1]);
|
|
1871
|
-
const
|
|
1872
|
-
|
|
1902
|
+
const updated = await this.db.transaction(async (query) => {
|
|
1903
|
+
const res = await query(`update auth.users set ${sets.join(", ")}, updated_at = now() where id = $${params.length} returning *`, params);
|
|
1904
|
+
const user = res.rows[0];
|
|
1905
|
+
if (!user)
|
|
1906
|
+
return null;
|
|
1907
|
+
if (typeof body.email === "string")
|
|
1908
|
+
await this.ensureEmailIdentity(user, query);
|
|
1909
|
+
return this.userJsonWithRelations(user, query);
|
|
1910
|
+
});
|
|
1911
|
+
if (!updated)
|
|
1873
1912
|
return authError(404, "user_not_found", "User not found");
|
|
1874
|
-
return json(200,
|
|
1913
|
+
return json(200, updated);
|
|
1875
1914
|
}
|
|
1876
1915
|
if (idMatch && method === "DELETE") {
|
|
1877
1916
|
return await this.eraseUser(idMatch[1]);
|
|
@@ -2067,17 +2106,26 @@ Or sign in with this link: ${link}`
|
|
|
2067
2106
|
if (!await verifyTotp(factor.secret, body.code ?? "")) {
|
|
2068
2107
|
return authError(422, "mfa_verification_failed", "Invalid TOTP code entered");
|
|
2069
2108
|
}
|
|
2070
|
-
await this.db.
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2109
|
+
const session = await this.db.transaction(async (query) => {
|
|
2110
|
+
const claimed = await query(`update auth.mfa_challenges set verified_at = now()
|
|
2111
|
+
where id = $1 and factor_id = $2 and verified_at is null and expires_at >= now()
|
|
2112
|
+
returning id`, [challenge.id, factorId]);
|
|
2113
|
+
if (claimed.rows.length === 0)
|
|
2114
|
+
return null;
|
|
2115
|
+
if (factor.status !== "verified") {
|
|
2116
|
+
await query(`update auth.mfa_factors set status = 'verified', updated_at = now() where id = $1`, [factorId]);
|
|
2117
|
+
}
|
|
2118
|
+
return this.sessionFor(user, undefined, {
|
|
2119
|
+
aal: "aal2",
|
|
2120
|
+
amr: [
|
|
2121
|
+
{ method: "password", timestamp: Math.floor(Date.now() / 1000) },
|
|
2122
|
+
{ method: "totp", timestamp: Math.floor(Date.now() / 1000) }
|
|
2123
|
+
]
|
|
2124
|
+
}, query);
|
|
2080
2125
|
});
|
|
2126
|
+
if (!session) {
|
|
2127
|
+
return authError(422, "mfa_verification_failed", "This challenge has already been verified");
|
|
2128
|
+
}
|
|
2081
2129
|
return json(200, session);
|
|
2082
2130
|
}
|
|
2083
2131
|
async unenrollFactor(req, factorId) {
|
|
@@ -2115,6 +2163,17 @@ Or sign in with this link: ${link}`
|
|
|
2115
2163
|
updated_at: iso(r.updated_at)
|
|
2116
2164
|
}));
|
|
2117
2165
|
}
|
|
2166
|
+
async ensureEmailIdentity(user, query = (sql, params) => this.db.query(sql, params)) {
|
|
2167
|
+
if (!user.email)
|
|
2168
|
+
return;
|
|
2169
|
+
await query(`insert into auth.identities (user_id, provider, provider_id, identity_data)
|
|
2170
|
+
values ($1::uuid, 'email', $1::text, $2::jsonb)
|
|
2171
|
+
on conflict (provider, provider_id) do update
|
|
2172
|
+
set identity_data = excluded.identity_data, updated_at = now()`, [user.id, JSON.stringify({ sub: user.id, email: user.email })]);
|
|
2173
|
+
}
|
|
2174
|
+
async userJsonWithRelations(user, query = (sql, params) => this.db.query(sql, params)) {
|
|
2175
|
+
return this.userJson(user, await this.getUserFactors(user.id, query), await this.getUserIdentities(user.id, query));
|
|
2176
|
+
}
|
|
2118
2177
|
async rotateRefreshToken(token) {
|
|
2119
2178
|
return this.db.transaction(async (query) => {
|
|
2120
2179
|
const claimedToken = await this.claimRefreshToken(query, token);
|
|
@@ -2188,9 +2247,14 @@ Or sign in with this link: ${link}`
|
|
|
2188
2247
|
is_anonymous: u.is_anonymous ?? false
|
|
2189
2248
|
};
|
|
2190
2249
|
}
|
|
2191
|
-
|
|
2250
|
+
sessionForOAuth(user) {
|
|
2251
|
+
return this.sessionFor(user, undefined, {
|
|
2252
|
+
amr: [{ method: "oauth", timestamp: Math.floor(Date.now() / 1000) }]
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
async oauthSessionTokensFor(userId) {
|
|
2192
2256
|
const res = await this.db.query(`select * from auth.users where id = $1`, [userId]);
|
|
2193
|
-
const session = await this.
|
|
2257
|
+
const session = await this.sessionForOAuth(res.rows[0]);
|
|
2194
2258
|
return { access_token: session.access_token, refresh_token: session.refresh_token, expires_in: session.expires_in };
|
|
2195
2259
|
}
|
|
2196
2260
|
async sessionFor(user, parentToken, opts, query = (sql, params) => this.db.query(sql, params)) {
|
|
@@ -10745,6 +10809,7 @@ function toPgArrayLiteral(array) {
|
|
|
10745
10809
|
|
|
10746
10810
|
// src/runtime/node/native/engine.ts
|
|
10747
10811
|
var DEFAULT_PG_VERSION = "17.7.0";
|
|
10812
|
+
var POSTGRES_MIRROR_URL_ERROR = "SUPACLOUD_LITE_POSTGRES_MIRROR must be an absolute HTTPS URL or a loopback HTTP URL";
|
|
10748
10813
|
var NATIVE_POSTGRES_MAJOR = DEFAULT_PG_VERSION.split(".")[0];
|
|
10749
10814
|
function isNativeEngineSupported() {
|
|
10750
10815
|
return (process.platform === "darwin" || process.platform === "linux") && (process.arch === "arm64" || process.arch === "x64") && (process.platform !== "linux" || isGlibcLinux());
|
|
@@ -10776,7 +10841,7 @@ var PINNED_SHA256 = {
|
|
|
10776
10841
|
"postgresql-17.7.0-x86_64-apple-darwin": "0dd8c25173524bad4ae8ef6b970da1ac40f4c1f231150c416ccb8cd06feff8f2",
|
|
10777
10842
|
"postgresql-17.7.0-aarch64-apple-darwin": "727ac08d20a704014a0d51eb3300aa0c8e292c1cf0a1c99d4f4b1002e1420220"
|
|
10778
10843
|
};
|
|
10779
|
-
async function verifyTarball(tarball, key,
|
|
10844
|
+
async function verifyTarball(tarball, key, upstreamUrl, downloadMirror) {
|
|
10780
10845
|
const actual = createHash2("sha256").update(readFileSync(tarball)).digest("hex");
|
|
10781
10846
|
const pinned = PINNED_SHA256[key];
|
|
10782
10847
|
if (pinned) {
|
|
@@ -10785,7 +10850,8 @@ async function verifyTarball(tarball, key, url) {
|
|
|
10785
10850
|
}
|
|
10786
10851
|
return;
|
|
10787
10852
|
}
|
|
10788
|
-
const
|
|
10853
|
+
const checksumUrl = resolvePostgresDownloadUrl(`${upstreamUrl}.sha256`, downloadMirror);
|
|
10854
|
+
const res = await fetchRelease(checksumUrl);
|
|
10789
10855
|
if (!res.ok)
|
|
10790
10856
|
throw new Error(`could not fetch checksum for ${key}: HTTP ${res.status}`);
|
|
10791
10857
|
const expected = (await res.text()).trim().split(/\s+/)[0].toLowerCase();
|
|
@@ -10795,13 +10861,14 @@ async function verifyTarball(tarball, key, url) {
|
|
|
10795
10861
|
throw new Error(`postgres binary checksum mismatch for ${key}: expected ${expected}, got ${actual}`);
|
|
10796
10862
|
}
|
|
10797
10863
|
}
|
|
10798
|
-
async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
|
|
10864
|
+
async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log, downloadMirror) {
|
|
10799
10865
|
const t = target();
|
|
10800
10866
|
const root = cacheDir ?? join2(homedir(), ".cache", "supacloud-lite");
|
|
10801
10867
|
const dir = join2(root, `postgresql-${version}-${t}`);
|
|
10802
10868
|
if (isCompleteInstall(dir))
|
|
10803
10869
|
return dir;
|
|
10804
|
-
const
|
|
10870
|
+
const upstreamUrl = `https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/postgresql-${version}-${t}.tar.gz`;
|
|
10871
|
+
const url = resolvePostgresDownloadUrl(upstreamUrl, downloadMirror);
|
|
10805
10872
|
mkdirSync(root, { recursive: true });
|
|
10806
10873
|
const uniq = `${process.pid}-${randomBytes2(6).toString("hex")}`;
|
|
10807
10874
|
const tarball = join2(root, `pg-${version}-${uniq}.tar.gz`);
|
|
@@ -10814,7 +10881,7 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
|
|
|
10814
10881
|
if (!res.ok)
|
|
10815
10882
|
throw new Error(`failed to download ${url}: HTTP ${res.status}`);
|
|
10816
10883
|
await writeFile2(tarball, Buffer.from(await res.arrayBuffer()));
|
|
10817
|
-
await verifyTarball(tarball, `postgresql-${version}-${t}`,
|
|
10884
|
+
await verifyTarball(tarball, `postgresql-${version}-${t}`, upstreamUrl, downloadMirror);
|
|
10818
10885
|
mkdirSync(tmpDir, { recursive: true });
|
|
10819
10886
|
await extractTar({ cwd: tmpDir, file: tarball, gzip: true, preserveOwner: false, strict: true, strip: 1 });
|
|
10820
10887
|
if (!isCompleteInstall(tmpDir))
|
|
@@ -10834,6 +10901,32 @@ async function ensurePostgres(version = DEFAULT_PG_VERSION, cacheDir, log) {
|
|
|
10834
10901
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
10835
10902
|
}
|
|
10836
10903
|
}
|
|
10904
|
+
function resolvePostgresDownloadUrl(upstreamUrl, downloadMirror) {
|
|
10905
|
+
const mirror = postgresDownloadMirror(downloadMirror);
|
|
10906
|
+
return mirror ? `${mirror.toString().replace(/\/+$/, "")}/${upstreamUrl}` : upstreamUrl;
|
|
10907
|
+
}
|
|
10908
|
+
function postgresDownloadMirror(downloadMirror) {
|
|
10909
|
+
const configuredMirror = downloadMirror ?? process.env.SUPACLOUD_LITE_POSTGRES_MIRROR;
|
|
10910
|
+
const mirrorText = configuredMirror?.trim();
|
|
10911
|
+
if (!mirrorText)
|
|
10912
|
+
return;
|
|
10913
|
+
let mirror;
|
|
10914
|
+
try {
|
|
10915
|
+
mirror = new URL(mirrorText);
|
|
10916
|
+
} catch {
|
|
10917
|
+
throw new Error(POSTGRES_MIRROR_URL_ERROR);
|
|
10918
|
+
}
|
|
10919
|
+
const loopbackHttp = mirror.protocol === "http:" && (mirror.hostname === "127.0.0.1" || mirror.hostname === "localhost" || mirror.hostname === "[::1]");
|
|
10920
|
+
if (mirror.protocol !== "https:" && !loopbackHttp)
|
|
10921
|
+
throw new Error(POSTGRES_MIRROR_URL_ERROR);
|
|
10922
|
+
if (mirror.username || mirror.password) {
|
|
10923
|
+
throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain credentials");
|
|
10924
|
+
}
|
|
10925
|
+
if (mirrorText.includes("?") || mirrorText.includes("#")) {
|
|
10926
|
+
throw new Error("SUPACLOUD_LITE_POSTGRES_MIRROR must not contain a query or fragment");
|
|
10927
|
+
}
|
|
10928
|
+
return mirror;
|
|
10929
|
+
}
|
|
10837
10930
|
async function fetchRelease(url) {
|
|
10838
10931
|
let lastError;
|
|
10839
10932
|
for (let attempt = 1;attempt <= 3; attempt++) {
|
|
@@ -10866,7 +10959,7 @@ async function createNativeEngine(opts) {
|
|
|
10866
10959
|
let postgres;
|
|
10867
10960
|
let removeExitHandler;
|
|
10868
10961
|
try {
|
|
10869
|
-
const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log);
|
|
10962
|
+
const installDir = await ensurePostgres(opts.version, opts.cacheDir, opts.log, opts.downloadMirror);
|
|
10870
10963
|
const bin = (name) => join2(installDir, "bin", name);
|
|
10871
10964
|
if (!existsSync(join2(opts.dataDir, "PG_VERSION"))) {
|
|
10872
10965
|
mkdirSync(opts.dataDir, { recursive: true });
|
|
@@ -97,6 +97,7 @@ export declare class AuthHandler {
|
|
|
97
97
|
private phoneDeliveriesSettledBeforeDeadline;
|
|
98
98
|
/** Dispatch one `/auth/v1/*` request. Any thrown error becomes a 500 `unexpected_failure`. */
|
|
99
99
|
handle(req: Request, ctx: RequestContext, url: URL): Promise<Response>;
|
|
100
|
+
private oauthCallbackUrl;
|
|
100
101
|
private signup;
|
|
101
102
|
private token;
|
|
102
103
|
private getUser;
|
|
@@ -159,6 +160,8 @@ export declare class AuthHandler {
|
|
|
159
160
|
private getUserFactors;
|
|
160
161
|
/** GoTrue-shaped identities for a user (linked providers), from auth.identities. */
|
|
161
162
|
private getUserIdentities;
|
|
163
|
+
private ensureEmailIdentity;
|
|
164
|
+
private userJsonWithRelations;
|
|
162
165
|
private rotateRefreshToken;
|
|
163
166
|
private claimRefreshToken;
|
|
164
167
|
private refreshSessionStartedAt;
|
|
@@ -167,8 +170,8 @@ export declare class AuthHandler {
|
|
|
167
170
|
private claimsFromBearer;
|
|
168
171
|
/** Shape a user row into the GoTrue user object supabase-js expects. */
|
|
169
172
|
userJson(u: UserRow, factors?: Record<string, unknown>[], identities?: Record<string, unknown>[]): Record<string, unknown>;
|
|
170
|
-
|
|
171
|
-
private
|
|
173
|
+
private sessionForOAuth;
|
|
174
|
+
private oauthSessionTokensFor;
|
|
172
175
|
private sessionFor;
|
|
173
176
|
}
|
|
174
177
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/runtime/auth/handler.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/runtime/auth/handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,10 +10,17 @@ export interface NativeEngineOptions {
|
|
|
10
10
|
cacheDir?: string;
|
|
11
11
|
/** sink for progress lines (download, install); no-op when omitted */
|
|
12
12
|
log?: (msg: string) => void;
|
|
13
|
+
/** Optional HTTPS or loopback HTTP prefix for proxying the PostgreSQL release download. */
|
|
14
|
+
downloadMirror?: string;
|
|
13
15
|
}
|
|
14
16
|
export declare function isNativeEngineSupported(): boolean;
|
|
15
17
|
/** Download + unpack Postgres binaries if not already cached (concurrency-safe). Returns the install dir. */
|
|
16
|
-
export declare function ensurePostgres(version?: string, cacheDir?: string, log?: (m: string) => void): Promise<string>;
|
|
18
|
+
export declare function ensurePostgres(version?: string, cacheDir?: string, log?: (m: string) => void, downloadMirror?: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Prefix a trusted GitHub release URL with an operator-selected mirror. The
|
|
21
|
+
* archive is still checked against the pinned or published SHA-256 before use.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolvePostgresDownloadUrl(upstreamUrl: string, downloadMirror?: string): string;
|
|
17
24
|
/**
|
|
18
25
|
* Boot the embedded Postgres child (initdb on first run) and return a {@link DbEngine}
|
|
19
26
|
* backed by two wire connections: one for queries/transactions (serialized by a mutex),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../../src/runtime/node/native/engine.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../../src/runtime/node/native/engine.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAOlD,eAAO,MAAM,qBAAqB,QAAoC,CAAA;AAEtE,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,sEAAsE;IACtE,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAIjD;AA6DD,6GAA6G;AAC7G,wBAAsB,cAAc,CAClC,OAAO,SAAqB,EAC5B,QAAQ,CAAC,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EACzB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CA4CjB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAG/F;AAkDD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAiFrF"}
|