fix: return proper token response from casdoor-callback API route + remove hardcoded secret
This commit is contained in:
@@ -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 = "/**"
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user