update
This commit is contained in:
@@ -7,7 +7,8 @@ import {
|
||||
getUserInfo,
|
||||
} from "../auth/oidc";
|
||||
import { requireAuth, syncUser } from "../middleware/auth";
|
||||
import type { AuthenticatedRequest } from "../types";
|
||||
import type { AuthenticatedRequest, AuthenticatedUser } from "../types";
|
||||
import type { AuthSession } from "../auth/oidc";
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -21,6 +22,26 @@ async function ensureOIDC() {
|
||||
}
|
||||
}
|
||||
|
||||
// Short-lived store for native app OIDC sessions, keyed by state.
|
||||
// Entries are cleaned up after 10 minutes regardless of use.
|
||||
const nativeOidcSessions = new Map<string, { session: AuthSession; expiresAt: number }>();
|
||||
const NATIVE_SESSION_TTL_MS = 10 * 60 * 1000;
|
||||
|
||||
function storeNativeSession(session: AuthSession): void {
|
||||
nativeOidcSessions.set(session.state, {
|
||||
session,
|
||||
expiresAt: Date.now() + NATIVE_SESSION_TTL_MS,
|
||||
});
|
||||
}
|
||||
|
||||
function popNativeSession(state: string): AuthSession | null {
|
||||
const entry = nativeOidcSessions.get(state);
|
||||
if (!entry) return null;
|
||||
nativeOidcSessions.delete(state);
|
||||
if (Date.now() > entry.expiresAt) return null;
|
||||
return entry.session;
|
||||
}
|
||||
|
||||
// GET /auth/login - Initiate OIDC login flow
|
||||
router.get("/login", async (req, res) => {
|
||||
try {
|
||||
@@ -28,7 +49,15 @@ router.get("/login", async (req, res) => {
|
||||
|
||||
const redirectUri = req.query.redirect_uri as string | undefined;
|
||||
const session = createAuthSession(redirectUri);
|
||||
|
||||
if (redirectUri) {
|
||||
// Native app flow: store session by state so /auth/token can retrieve it
|
||||
// without relying on the browser cookie jar.
|
||||
storeNativeSession(session);
|
||||
} else {
|
||||
// Web flow: store session in the cookie-backed server session as before.
|
||||
req.session.oidc = session;
|
||||
}
|
||||
|
||||
const authorizationUrl = getAuthorizationUrl(session, redirectUri);
|
||||
res.redirect(authorizationUrl);
|
||||
@@ -38,7 +67,7 @@ router.get("/login", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /auth/callback - OIDC callback handler
|
||||
// GET /auth/callback - OIDC callback handler (web frontend only)
|
||||
router.get("/callback", async (req, res) => {
|
||||
try {
|
||||
await ensureOIDC();
|
||||
@@ -89,9 +118,9 @@ router.get("/me", requireAuth, (req: AuthenticatedRequest, res) => {
|
||||
res.json(req.user);
|
||||
});
|
||||
|
||||
// POST /auth/token - Exchange authorization code for tokens (for native apps)
|
||||
// The session cookie set during /auth/login is sent automatically by ASWebAuthenticationSession,
|
||||
// so req.session.oidc contains the original state/nonce for validation.
|
||||
// POST /auth/token - Exchange authorization code for tokens (native app flow)
|
||||
// Session state is retrieved from the in-memory store by state value, so no
|
||||
// session cookie is required from the native client.
|
||||
router.post("/token", async (req, res) => {
|
||||
try {
|
||||
await ensureOIDC();
|
||||
@@ -103,20 +132,12 @@ router.post("/token", async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const oidcSession = req.session.oidc;
|
||||
const oidcSession = popNativeSession(state);
|
||||
if (!oidcSession) {
|
||||
res.status(400).json({ error: "No active OIDC session. Initiate login first." });
|
||||
res.status(400).json({ error: "OIDC session not found or expired. Initiate login again." });
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate that the returned state matches what was stored in the session
|
||||
if (oidcSession.state !== state) {
|
||||
res.status(400).json({ error: "State mismatch" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the session's own codeVerifier (generated by the backend during /auth/login)
|
||||
// rather than a client-supplied one, to prevent verifier substitution attacks.
|
||||
const session = {
|
||||
codeVerifier: oidcSession.codeVerifier,
|
||||
state: oidcSession.state,
|
||||
@@ -129,9 +150,6 @@ router.post("/token", async (req, res) => {
|
||||
const user = await getUserInfo(tokenSet);
|
||||
await syncUser(user);
|
||||
|
||||
// Clear OIDC session state now that it has been consumed
|
||||
delete req.session.oidc;
|
||||
|
||||
res.json({
|
||||
access_token: tokenSet.access_token,
|
||||
id_token: tokenSet.id_token,
|
||||
|
||||
Reference in New Issue
Block a user