@dashai/cli 0.10.0 → 0.11.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/dist/bin.js +61 -21
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -929,6 +929,15 @@ export default withAuth({
|
|
|
929
929
|
// redirect-loop trying to re-authenticate.
|
|
930
930
|
publicPaths: ['/', '/about', '/sign-in-error'],
|
|
931
931
|
loginPath: '/api/auth/sign-in',
|
|
932
|
+
// Silent sign-in: when a signed-in DashWise user opens the module through
|
|
933
|
+
// the platform (in-app embed or the module subdomain), the SSO handshake
|
|
934
|
+
// completes with zero clicks \u2014 they land straight in the app, landing page
|
|
935
|
+
// included. It never touches:
|
|
936
|
+
// - anonymous visitors on public installs (no platform-session hint),
|
|
937
|
+
// - local \`dashwise dev\` (no platform proxy \u2192 no hint header) \u2014 the
|
|
938
|
+
// landing's "Sign in with DashWise" button stays your dev flow,
|
|
939
|
+
// - anything within 60s of a failed silent attempt (no redirect loops).
|
|
940
|
+
autoSignIn: true,
|
|
932
941
|
});
|
|
933
942
|
|
|
934
943
|
export const config = {
|
|
@@ -970,11 +979,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
970
979
|
function appPageCustomTsx(opts) {
|
|
971
980
|
return `// Public landing page \u2014 listed in middleware.ts publicPaths.
|
|
972
981
|
//
|
|
973
|
-
//
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
//
|
|
977
|
-
//
|
|
982
|
+
// NOTE: signed-in DashWise users usually never see this page \u2014 the
|
|
983
|
+
// middleware's \`autoSignIn\` signs them in silently and they land on the
|
|
984
|
+
// page they asked for. This landing serves anonymous visitors (public
|
|
985
|
+
// installs), local \`dashwise dev\`, and the rare failed silent attempt \u2014
|
|
986
|
+
// its "Sign in with DashWise" button is the manual fallback that kicks
|
|
987
|
+
// off the same SSO flow (via /api/auth/sign-in). After signing in, the
|
|
988
|
+
// user lands on /dashboard (Server Component, session-validated).
|
|
978
989
|
//
|
|
979
990
|
// Styling: shadcn/ui primitives (\`<Button>\`, \`<Card>\`) on Tailwind 4
|
|
980
991
|
// theme tokens. Add more blocks via \`npx shadcn add @shadcn-studio/...\`
|
|
@@ -1190,6 +1201,11 @@ import { getPublicOrigin } from '@/lib/request-origin';
|
|
|
1190
1201
|
export async function GET(request: Request) {
|
|
1191
1202
|
const url = new URL(request.url);
|
|
1192
1203
|
const returnTo = url.searchParams.get('return_to') ?? '/';
|
|
1204
|
+
// \`auto=1\` marks a SILENT attempt (the middleware's autoSignIn redirect,
|
|
1205
|
+
// no user click). Its failures degrade to the public landing page \u2014 where
|
|
1206
|
+
// the manual "Sign in with DashWise" button lives \u2014 instead of the error
|
|
1207
|
+
// page: the user never asked for anything, so don't show them an error.
|
|
1208
|
+
const isAuto = url.searchParams.get('auto') === '1';
|
|
1193
1209
|
const apiBaseUrl = process.env.DASHWISE_API_URL;
|
|
1194
1210
|
const apiKey = process.env.DASHWISE_API_KEY;
|
|
1195
1211
|
|
|
@@ -1216,6 +1232,16 @@ export async function GET(request: Request) {
|
|
|
1216
1232
|
const callback = new URL('/api/auth/callback', moduleOrigin);
|
|
1217
1233
|
callback.searchParams.set('return_to', returnTo);
|
|
1218
1234
|
|
|
1235
|
+
// Where a failed start lands: the error page for user-initiated sign-ins,
|
|
1236
|
+
// the landing page for silent (auto) attempts.
|
|
1237
|
+
const failTarget = (code: string, description?: string): URL => {
|
|
1238
|
+
if (isAuto) return new URL('/', moduleOrigin);
|
|
1239
|
+
const target = new URL('/sign-in-error', moduleOrigin);
|
|
1240
|
+
target.searchParams.set('code', code);
|
|
1241
|
+
if (description) target.searchParams.set('description', description);
|
|
1242
|
+
return target;
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1219
1245
|
let res: Response;
|
|
1220
1246
|
try {
|
|
1221
1247
|
res = await fetch(\`\${apiBaseUrl}/api/module-api/sso/start-url\`, {
|
|
@@ -1228,10 +1254,9 @@ export async function GET(request: Request) {
|
|
|
1228
1254
|
body: JSON.stringify({ callback_url: callback.toString() }),
|
|
1229
1255
|
});
|
|
1230
1256
|
} catch (err) {
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
return NextResponse.redirect(target);
|
|
1257
|
+
return NextResponse.redirect(
|
|
1258
|
+
failTarget('server_error', (err as Error).message),
|
|
1259
|
+
);
|
|
1235
1260
|
}
|
|
1236
1261
|
|
|
1237
1262
|
if (!res.ok) {
|
|
@@ -1241,23 +1266,18 @@ export async function GET(request: Request) {
|
|
|
1241
1266
|
const body = (await res.json().catch(() => null)) as
|
|
1242
1267
|
| { code?: string; message?: string }
|
|
1243
1268
|
| null;
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
return NextResponse.redirect(target);
|
|
1269
|
+
return NextResponse.redirect(
|
|
1270
|
+
failTarget(body?.code ?? 'invalid_request', body?.message),
|
|
1271
|
+
);
|
|
1248
1272
|
}
|
|
1249
1273
|
|
|
1250
1274
|
const body = (await res.json().catch(() => null)) as
|
|
1251
1275
|
| { redirect_url?: string }
|
|
1252
1276
|
| null;
|
|
1253
1277
|
if (!body?.redirect_url) {
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
target.searchParams.set(
|
|
1257
|
-
'description',
|
|
1258
|
-
'start-url response missing redirect_url',
|
|
1278
|
+
return NextResponse.redirect(
|
|
1279
|
+
failTarget('server_error', 'start-url response missing redirect_url'),
|
|
1259
1280
|
);
|
|
1260
|
-
return NextResponse.redirect(target);
|
|
1261
1281
|
}
|
|
1262
1282
|
return NextResponse.redirect(body.redirect_url);
|
|
1263
1283
|
}
|
|
@@ -1293,7 +1313,13 @@ export async function GET(request: Request) {
|
|
|
1293
1313
|
return NextResponse.redirect(target);
|
|
1294
1314
|
}
|
|
1295
1315
|
const code = url.searchParams.get('code');
|
|
1296
|
-
|
|
1316
|
+
// Only ever redirect to a ROOT-RELATIVE, same-module path. \`return_to\`
|
|
1317
|
+
// rides through the SSO round-trip unvalidated (the backend checks only the
|
|
1318
|
+
// callback ORIGIN, not this sub-param), so an absolute (\`https://evil\`),
|
|
1319
|
+
// protocol-relative (\`//evil\`), or control-char-smuggled value would be a
|
|
1320
|
+
// post-auth open redirect off the module host. Reject anything that isn't a
|
|
1321
|
+
// clean root-relative path.
|
|
1322
|
+
const returnTo = safeReturnTo(url.searchParams.get('return_to'));
|
|
1297
1323
|
if (!code) {
|
|
1298
1324
|
const target = new URL('/sign-in-error', publicOrigin);
|
|
1299
1325
|
target.searchParams.set('code', 'missing_code');
|
|
@@ -1309,6 +1335,20 @@ export async function GET(request: Request) {
|
|
|
1309
1335
|
}
|
|
1310
1336
|
return NextResponse.redirect(new URL(returnTo, publicOrigin));
|
|
1311
1337
|
}
|
|
1338
|
+
|
|
1339
|
+
/** Root-relative in-module paths only \u2014 defends against open-redirect via
|
|
1340
|
+
* a crafted \`return_to\`. Rejects absolute/protocol-relative/backslash and
|
|
1341
|
+
* any ASCII control char (which WHATWG URL parsing strips, defeating a naive
|
|
1342
|
+
* prefix check). Falls back to '/'. */
|
|
1343
|
+
function safeReturnTo(raw: string | null): string {
|
|
1344
|
+
if (!raw || raw[0] !== '/') return '/';
|
|
1345
|
+
if (raw.startsWith('//') || raw.startsWith('/\\\\')) return '/';
|
|
1346
|
+
for (let i = 0; i < raw.length; i++) {
|
|
1347
|
+
const c = raw.charCodeAt(i);
|
|
1348
|
+
if (c < 0x20 || c === 0x7f) return '/';
|
|
1349
|
+
}
|
|
1350
|
+
return raw;
|
|
1351
|
+
}
|
|
1312
1352
|
`;
|
|
1313
1353
|
}
|
|
1314
1354
|
function appApiAuthSignOutTs() {
|
|
@@ -9231,7 +9271,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
9231
9271
|
}
|
|
9232
9272
|
})();
|
|
9233
9273
|
function getVersion() {
|
|
9234
|
-
return "0.
|
|
9274
|
+
return "0.11.0";
|
|
9235
9275
|
}
|
|
9236
9276
|
function parseIntOption(value) {
|
|
9237
9277
|
const n = Number.parseInt(value, 10);
|