actions.ts84 lines · main
1'use server';
2
3import { redirect } from 'next/navigation';
4
5import { apiFetch } from '../../../lib/api';
6
7interface CliTokenResp {
8 token?: string;
9 message?: string;
10 code?: string;
11}
12
13function isLoopbackHttp(url: string): boolean {
14 try {
15 const u = new URL(url);
16 return (
17 u.protocol === 'http:' &&
18 (u.hostname === '127.0.0.1' || u.hostname === 'localhost') &&
19 u.port.length > 0
20 );
21 } catch {
22 return false;
23 }
24}
25
26/**
27 * User clicked Allow — mint a 24h CLI token and send the browser back to the
28 * local CLI callback (http://127.0.0.1:port/cb?token=…&state=…).
29 *
30 * On failure we redirect back to /cli-auth with ?error=… so the user sees a
31 * clear message instead of the global 500 page.
32 */
33export async function allow({
34 redirectUrl,
35 state,
36}: {
37 redirectUrl: string;
38 state: string;
39}): Promise<void> {
40 if (!isLoopbackHttp(redirectUrl) || state.length === 0 || state.length > 256) {
41 redirect('/cli-auth?error=bad_request');
42 }
43
44 const res = await apiFetch('/v1/auth/cli-token', { method: 'POST' });
45 if (res.status === 401) {
46 const back = `/cli-auth?redirect=${encodeURIComponent(redirectUrl)}&state=${encodeURIComponent(state)}`;
47 redirect(`/signin?next=${encodeURIComponent(back)}`);
48 }
49 if (!res.ok) {
50 const body = (await res.json().catch(() => ({}))) as CliTokenResp;
51 const code = encodeURIComponent(
52 body.code ?? body.message ?? `mint_failed_${res.status}`,
53 );
54 redirect(
55 `/cli-auth?redirect=${encodeURIComponent(redirectUrl)}&state=${encodeURIComponent(state)}&error=${code}`,
56 );
57 }
58 const body = (await res.json()) as CliTokenResp;
59 if (!body.token) {
60 redirect(
61 `/cli-auth?redirect=${encodeURIComponent(redirectUrl)}&state=${encodeURIComponent(state)}&error=no_token`,
62 );
63 }
64 const u = new URL(redirectUrl);
65 u.searchParams.set('token', body.token);
66 u.searchParams.set('state', state);
67 redirect(u.toString());
68}
69
70export async function deny({
71 redirectUrl,
72 state,
73}: {
74 redirectUrl: string;
75 state: string;
76}): Promise<void> {
77 if (!isLoopbackHttp(redirectUrl)) {
78 redirect('/cli-auth?error=bad_request');
79 }
80 const u = new URL(redirectUrl);
81 u.searchParams.set('denied', '1');
82 u.searchParams.set('state', state);
83 redirect(u.toString());
84}