feat(frontend): add React UI — procurement office, wizard, suppliers, inventory panel (dash port), role-based login
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
// ============================================================
|
||||
// Casdoor OIDC + PKCE login — v2.6
|
||||
// Config lives in /casdoor-config.js (window.CASDOOR_CONFIG), served
|
||||
// statically next to index.html (same values as the shipped dash).
|
||||
// ============================================================
|
||||
|
||||
const CASDOOR_CONFIG = (window as unknown as { CASDOOR_CONFIG?: CasdoorConfig }).CASDOOR_CONFIG;
|
||||
|
||||
export interface CasdoorConfig {
|
||||
serverUrl: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
organization: string;
|
||||
appName: string;
|
||||
redirectUri: string;
|
||||
}
|
||||
|
||||
function b64url(bytes: Uint8Array): string {
|
||||
let s = "";
|
||||
for (const b of bytes) s += String.fromCharCode(b);
|
||||
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
||||
}
|
||||
|
||||
function randomBytes(n: number): Uint8Array {
|
||||
const arr = new Uint8Array(n);
|
||||
crypto.getRandomValues(arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
async function sha256b64url(text: string): Promise<string> {
|
||||
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
||||
return b64url(new Uint8Array(buf));
|
||||
}
|
||||
|
||||
export function startCasdoorLogin(): void {
|
||||
if (!CASDOOR_CONFIG) {
|
||||
alert("تنظیمات Casdoor یافت نشد — فایل casdoor-config.js را بررسی کنید");
|
||||
return;
|
||||
}
|
||||
const verifier = b64url(randomBytes(64));
|
||||
const state = b64url(randomBytes(16));
|
||||
sessionStorage.setItem("casdoor-pkce", JSON.stringify({ verifier, state }));
|
||||
void sha256b64url(verifier).then((challenge) => {
|
||||
const params = new URLSearchParams({
|
||||
client_id: CASDOOR_CONFIG.clientId,
|
||||
redirect_uri: CASDOOR_CONFIG.redirectUri,
|
||||
response_type: "code",
|
||||
scope: "read",
|
||||
state,
|
||||
organization: CASDOOR_CONFIG.organization,
|
||||
application: CASDOOR_CONFIG.appName,
|
||||
code_challenge: challenge,
|
||||
code_challenge_method: "S256",
|
||||
});
|
||||
window.location.href = `${CASDOOR_CONFIG.serverUrl}/login/oauth/authorize?${params.toString()}`;
|
||||
});
|
||||
}
|
||||
|
||||
/** Exchange the OIDC authorization code for a token (called on /callback). */
|
||||
export async function exchangeCodeForToken(code: string): Promise<{ access_token: string; id_token?: string }> {
|
||||
if (!CASDOOR_CONFIG) throw new Error("تنظیمات Casdoor یافت نشد");
|
||||
const pkce = JSON.parse(sessionStorage.getItem("casdoor-pkce") || "{}") as { verifier?: string };
|
||||
const resp = await fetch(`${CASDOOR_CONFIG.serverUrl}/api/login/oauth/access_token`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
client_id: CASDOOR_CONFIG.clientId,
|
||||
client_secret: CASDOOR_CONFIG.clientSecret,
|
||||
code,
|
||||
redirect_uri: CASDOOR_CONFIG.redirectUri,
|
||||
code_verifier: pkce.verifier || "",
|
||||
}),
|
||||
});
|
||||
if (!resp.ok) throw new Error("خطا در دریافت توکن از Casdoor");
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
export { CASDOOR_CONFIG };
|
||||
Reference in New Issue
Block a user