integrate login page wih api and keycloak
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { createError } from 'h3';
|
||||
import type { AuthInfoResponse } from '~/types/auth';
|
||||
import {
|
||||
createUserSession,
|
||||
deleteUserSession,
|
||||
getUserSession,
|
||||
} from '~/server/utils/sessionStore';
|
||||
|
||||
type SessionStoreRequest = {
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
const DEFAULT_SESSION_SECONDS = 60 * 60; // 1 hour
|
||||
|
||||
|
||||
const parseJwtPayload = (token: string): { exp?: number } | null => {
|
||||
try {
|
||||
const payload = token.split('.')[1];
|
||||
if (!payload) return null;
|
||||
|
||||
const normalized = payload.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
|
||||
const decoded = Buffer.from(padded, 'base64').toString();
|
||||
|
||||
return JSON.parse(decoded) as { exp?: number };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody<SessionStoreRequest>(event);
|
||||
|
||||
const accessToken = body?.accessToken;
|
||||
const refreshToken = body?.refreshToken;
|
||||
|
||||
if (!accessToken || !refreshToken) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'accessToken and refreshToken are required',
|
||||
});
|
||||
}
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const baseUrl = config.public.baseUrl;
|
||||
const authInfoUrl = `${baseUrl}/api/v1/auth/info`;
|
||||
|
||||
let authInfoResponse: AuthInfoResponse;
|
||||
try {
|
||||
authInfoResponse = await $fetch<AuthInfoResponse>(authInfoUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage:
|
||||
error?.data?.message || error?.message || 'Failed to fetch auth info',
|
||||
});
|
||||
}
|
||||
|
||||
if (!authInfoResponse?.data) {
|
||||
throw createError({
|
||||
statusCode: 502,
|
||||
statusMessage: 'Invalid auth info response',
|
||||
});
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const refreshPayload = parseJwtPayload(refreshToken);
|
||||
const refreshExpSeconds = refreshPayload?.exp;
|
||||
|
||||
const expiresAt =
|
||||
typeof refreshExpSeconds === 'number' && refreshExpSeconds > 0
|
||||
? refreshExpSeconds * 1000
|
||||
: now + DEFAULT_SESSION_SECONDS * 1000;
|
||||
|
||||
const maxAge = Math.max(Math.floor((expiresAt - now) / 1000), 60);
|
||||
|
||||
const previousSessionId = getCookie(event, 'user_session');
|
||||
if (previousSessionId && getUserSession(previousSessionId)) {
|
||||
deleteUserSession(previousSessionId);
|
||||
}
|
||||
|
||||
const sessionId = createUserSession({
|
||||
user: {
|
||||
auth_provider: authInfoResponse.data.auth_provider || 'jwt',
|
||||
email: authInfoResponse.data.email,
|
||||
name: authInfoResponse.data.name,
|
||||
role: authInfoResponse.data.role,
|
||||
user_id: authInfoResponse.data.user_id,
|
||||
username: authInfoResponse.data.username,
|
||||
},
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresAt,
|
||||
});
|
||||
|
||||
const isSecure =
|
||||
process.env.NODE_ENV === 'production' && event.node.req.headers['x-forwarded-proto'] === 'https';
|
||||
|
||||
setCookie(event, 'user_session', sessionId, {
|
||||
httpOnly: true,
|
||||
secure: isSecure,
|
||||
sameSite: 'lax',
|
||||
maxAge,
|
||||
path: '/',
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Session stored successfully',
|
||||
expiresAt,
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user