integrate login page wih api and keycloak

This commit is contained in:
Yusron alamsyah
2026-03-31 11:11:39 +07:00
parent acf491f8aa
commit d438fb0f5f
68 changed files with 1213 additions and 212 deletions
+119
View File
@@ -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,
};
});