fix: return proper token response from casdoor-callback API route + remove hardcoded secret

This commit is contained in:
2026-06-30 01:14:42 +02:00
parent 4951f7f9e2
commit 03e0d5dc60
8 changed files with 146 additions and 60 deletions
View File
+6
View File
@@ -21,6 +21,12 @@
to = "/mobile/auth/callback" to = "/mobile/auth/callback"
status = 200 status = 200
# API routes need basePath prefix rewrite
[[redirects]]
from = "/api/*"
to = "/mobile/api/:splat"
status = 200
# Everything else handled by Next.js plugin # Everything else handled by Next.js plugin
[[redirects]] [[redirects]]
from = "/**" from = "/**"
+13
View File
@@ -0,0 +1,13 @@
// Health check — test if functions work at all
exports.handler = async () => {
return {
statusCode: 200,
body: JSON.stringify({
ok: true,
node: process.version,
hasFetch: typeof fetch === 'function',
cwd: process.cwd(),
envKeys: Object.keys(process.env).filter(k => !k.includes('SECRET') && !k.includes('PASSWORD')).sort(),
}),
};
};
+30
View File
@@ -0,0 +1,30 @@
export const handler = async () => {
const results = {};
try {
results.step1 = "started";
results.hasSecret = !!process.env.CASDOOR_CLIENT_SECRET;
results.hasClientId = !!process.env.NEXT_PUBLIC_CASDOOR_CLIENT_ID;
const r = await fetch("https://auth.falahos.my/api/login/oauth/access_token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: process.env.NEXT_PUBLIC_CASDOOR_CLIENT_ID || "",
client_secret: process.env.CASDOOR_CLIENT_SECRET || "",
code: "test",
redirect_uri: process.env.NEXT_PUBLIC_CASDOOR_REDIRECT_URI || "",
}),
});
results.tokenStatus = r.status;
results.tokenBody = await r.text();
} catch (e) {
results.error = e.message;
results.errorStack = e.stack?.substring(0, 200);
}
return {
statusCode: 200,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(results),
};
};
+10
View File
@@ -12,6 +12,16 @@ const nextConfig: NextConfig = {
turbopack: { turbopack: {
root: path.resolve(import.meta.dirname || __dirname), root: path.resolve(import.meta.dirname || __dirname),
}, },
async redirects() {
return [
{
source: "/auth/callback",
destination: "/mobile/auth/callback",
permanent: false,
basePath: false,
},
];
},
}; };
export default nextConfig; export default nextConfig;
+51 -58
View File
@@ -1,83 +1,76 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
const CLIENT_ID = process.env.NEXT_PUBLIC_CASDOOR_CLIENT_ID || "272f91105da5cf984773";
const CLIENT_SECRET = process.env.CASDOOR_CLIENT_SECRET;
const CASDOOR_TOKEN_URL = "https://auth.falahos.my/api/login/oauth/access_token";
const CASDOOR_USERINFO_URL = "https://auth.falahos.my/api/userinfo";
export async function GET(req: NextRequest) { export async function GET(req: NextRequest) {
try { try {
const code = req.nextUrl.searchParams.get("code"); const code = req.nextUrl.searchParams.get("code");
const state = req.nextUrl.searchParams.get("state");
if (!code) { if (!code) {
return NextResponse.json({ error: "Missing authorization code" }, { status: 400 }); return NextResponse.json({ error: "Missing code" }, { status: 400 });
} }
const clientId = process.env.NEXT_PUBLIC_CASDOOR_CLIENT_ID || "272f91105da5cf984773";
if (!CLIENT_SECRET) { const clientSecret = process.env.CASDOOR_CLIENT_SECRET;
return NextResponse.json({ error: "Casdoor client secret not configured" }, { status: 500 }); if (!clientSecret) {
return NextResponse.json({ error: "Secret not configured" }, { status: 500 });
} }
const redirectUri = process.env.NEXT_PUBLIC_CASDOOR_REDIRECT_URI || "https://mobile2.falahos.my/mobile/auth/callback";
// Exchange authorization code for tokens const params = new URLSearchParams();
const REDIRECT_URI = params.append("grant_type", "authorization_code");
process.env.NEXT_PUBLIC_CASDOOR_REDIRECT_URI || params.append("client_id", clientId);
"https://mobile2.falahos.my/mobile/auth/callback"; params.append("client_secret", clientSecret);
params.append("code", code);
params.append("redirect_uri", redirectUri);
const tokenRes = await fetch(CASDOOR_TOKEN_URL, { const r = await fetch("https://auth.falahos.my/api/login/oauth/access_token", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" }, headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ body: params,
grant_type: "authorization_code",
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code,
// redirect_uri must match exactly what was used in authorize request
redirect_uri: REDIRECT_URI,
}),
}); });
if (!tokenRes.ok) { const text = await r.text();
const errBody = await tokenRes.text(); let tokenData: Record<string, unknown> = {};
console.error("[casdoor-oidc] Token exchange failed:", tokenRes.status, errBody);
return NextResponse.json({ error: "Token exchange failed" }, { status: 502 }); try {
tokenData = JSON.parse(text);
} catch {
return NextResponse.json(
{ error: "Token exchange failed", detail: text.substring(0, 200) },
{ status: 502 }
);
} }
const tokenData = await tokenRes.json(); if (!r.ok || tokenData.error) {
const accessToken = tokenData.access_token; return NextResponse.json(
const idToken = tokenData.id_token; { error: tokenData.error_description || tokenData.error || "Token exchange failed" },
{ status: r.status }
if (!accessToken) { );
return NextResponse.json({ error: "No access_token in response" }, { status: 502 });
} }
// Fetch userinfo from Casdoor // Fetch user info with access token
const userRes = await fetch(CASDOOR_USERINFO_URL, { let user = null;
headers: { Authorization: `Bearer ${accessToken}` }, const accessToken = tokenData.access_token as string | undefined;
}); if (accessToken) {
try {
let userInfo = null; const u = await fetch("https://auth.falahos.my/api/userinfo", {
if (userRes.ok) { headers: { Authorization: "Bearer " + accessToken },
userInfo = await userRes.json(); });
if (u.ok) {
user = await u.json();
}
} catch {
// userinfo optional
}
} }
return NextResponse.json({ return NextResponse.json({
success: true,
access_token: accessToken, access_token: accessToken,
id_token: idToken, id_token: tokenData.id_token as string | undefined,
token_type: tokenData.token_type || "Bearer", refresh_token: tokenData.refresh_token as string | undefined,
expires_in: tokenData.expires_in || 3600, token_type: tokenData.token_type as string | undefined,
refresh_token: tokenData.refresh_token || null, expires_in: tokenData.expires_in as number | undefined,
user: userInfo user,
? {
id: userInfo.sub || userInfo.id,
email: userInfo.email,
name: userInfo.name || userInfo.displayName || userInfo.preferred_username,
avatar: userInfo.avatar || userInfo.picture,
}
: null,
}); });
} catch (error) { } catch (e) {
console.error("[casdoor-oidc] Callback error:", error); const msg = e instanceof Error ? e.message : "Error";
return NextResponse.json({ error: "Internal server error" }, { status: 500 }); return NextResponse.json({ error: msg }, { status: 500 });
} }
} }
+34
View File
@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from "next/server";
export async function GET(req: NextRequest) {
try {
const code = req.nextUrl.searchParams.get("code") || "none";
// Step 1: Just try the simplest fetch
const testUrl = "https://auth.falahos.my/api/login/oauth/access_token";
const params = new URLSearchParams();
params.append("grant_type", "authorization_code");
params.append("client_id", process.env.NEXT_PUBLIC_CASDOOR_CLIENT_ID || "");
params.append("client_secret", process.env.CASDOOR_CLIENT_SECRET || "");
params.append("code", code);
params.append("redirect_uri", (process.env.NEXT_PUBLIC_CASDOOR_REDIRECT_URI || "https://mobile2.falahos.my/mobile/auth/callback"));
const res = await fetch(testUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: params,
});
const body = await res.text();
return NextResponse.json({
ok: true,
code,
hasSecret: !!process.env.CASDOOR_CLIENT_SECRET,
casdoorStatus: res.status,
casdoorBody: body.substring(0, 300),
});
} catch (e: any) {
return NextResponse.json({ error: e.message, stack: e.stack?.substring(0, 500) }, { status: 500 });
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ function AuthCallbackContent() {
// --- Casdoor OIDC flow: exchange authorization code for tokens --- // --- Casdoor OIDC flow: exchange authorization code for tokens ---
if (code) { if (code) {
fetch(`/api/auth/casdoor-callback?code=${encodeURIComponent(code)}`) fetch(`/mobile/api/auth/casdoor-callback?code=${encodeURIComponent(code)}`)
.then((res) => res.json()) .then((res) => res.json())
.then((data) => { .then((data) => {
if (data.error) { if (data.error) {