Merge pull request #788 from ZacharyZcR/feat/system-browser-oidc-auth

feat: implement RFC 8252 system browser OIDC authentication for desktop
This commit is contained in:
ZacharyZcR
2026-05-18 04:35:55 +08:00
committed by GitHub
5 changed files with 68 additions and 3 deletions
+11 -2
View File
@@ -819,7 +819,7 @@ router.get("/oidc-config/admin", requireAdmin, async (req, res) => {
*/
router.get("/oidc/authorize", async (req, res) => {
try {
const { rememberMe } = req.query;
const { rememberMe, desktopCallbackPort } = req.query;
const origin = getRequestOriginWithForceHTTPS(req);
const backendCallbackUri = `${origin}/users/oidc/callback`;
@@ -842,7 +842,9 @@ router.get("/oidc/authorize", async (req, res) => {
const referer = req.get("Referer");
let frontendOrigin;
if (referer) {
if (desktopCallbackPort) {
frontendOrigin = `http://127.0.0.1:${desktopCallbackPort}/oidc-callback`;
} else if (referer) {
const refererUrl = new URL(referer);
frontendOrigin = `${refererUrl.protocol}//${refererUrl.host}`;
} else {
@@ -1365,6 +1367,8 @@ router.get("/oidc/callback", async (req, res) => {
const redirectUrl = new URL(frontendOrigin);
redirectUrl.searchParams.set("success", "true");
const isDesktopCallback = frontendOrigin.startsWith("http://127.0.0.1:");
const maxAge =
deviceInfo.type === "desktop" || deviceInfo.type === "mobile"
? 30 * 24 * 60 * 60 * 1000
@@ -1374,6 +1378,11 @@ router.get("/oidc/callback", async (req, res) => {
res.clearCookie("jwt", authManager.getClearCookieOptions(req));
if (isDesktopCallback) {
redirectUrl.searchParams.set("token", token);
return res.redirect(redirectUrl.toString());
}
return res
.cookie("jwt", token, authManager.getSecureCookieOptions(req, maxAge))
.redirect(redirectUrl.toString());
+16
View File
@@ -646,6 +646,22 @@ export function Auth({ onLogin }: AuthProps) {
async function handleOIDCLogin() {
setOidcLoading(true);
try {
if (isElectron()) {
const electronAPI = (window as unknown as { electronAPI?: { oidcSystemBrowserAuth?: (authUrl: string, port: number) => Promise<{ success: boolean; token?: string; error?: string }> } }).electronAPI;
if (electronAPI?.oidcSystemBrowserAuth) {
const callbackPort = 17832 + Math.floor(Math.random() * 100);
const authResponse = await getOIDCAuthorizeUrl(rememberMe, callbackPort);
const { auth_url: authUrl } = authResponse;
if (!authUrl) throw new Error(t("errors.invalidAuthUrl"));
const result = await electronAPI.oidcSystemBrowserAuth(authUrl, callbackPort);
if (result.success && result.token) {
localStorage.setItem("jwt_token", result.token);
window.location.reload();
return;
}
throw new Error(result.error || "Authentication failed");
}
}
const authResponse = await getOIDCAuthorizeUrl(rememberMe);
const { auth_url: authUrl } = authResponse;
if (!authUrl || authUrl === "undefined")
+2 -1
View File
@@ -3121,10 +3121,11 @@ export async function changePassword(oldPassword: string, newPassword: string) {
export async function getOIDCAuthorizeUrl(
rememberMe = false,
desktopCallbackPort?: number,
): Promise<OIDCAuthorize> {
try {
const response = await authApi.get("/users/oidc/authorize", {
params: { rememberMe },
params: { rememberMe, desktopCallbackPort },
});
return response.data;
} catch (error) {