diff --git a/website/.env.example b/website/.env.example index 2a59ba0..a06c8e0 100644 --- a/website/.env.example +++ b/website/.env.example @@ -6,8 +6,8 @@ APP_KEY= NODE_ENV=development SESSION_DRIVER=cookie -OIDC_REDIRECT_URI= -OIDC_DISCOVERY_ENDPOINT= -OIDC_TOKEN_ENDPOINT= +OIDC_REDIRECT_URI=http://localhost:3333/keycloak/callback +OIDC_DISCOVERY_ENDPOINT=http://localhost:8080/realms/enei/.well-known/openid-configuration +OIDC_TOKEN_ENDPOINT=http://localhost:8080/realms/enei/protocol/openid-connect/token OIDC_CLIENT_ID=enei-website -OIDC_CLIENT_SECRET= +OIDC_CLIENT_SECRET=example diff --git a/website/app/controllers/oidc_controller.ts b/website/app/controllers/oidc_controller.ts index e3b2385..ea8df70 100644 --- a/website/app/controllers/oidc_controller.ts +++ b/website/app/controllers/oidc_controller.ts @@ -1,12 +1,13 @@ import User from '#models/user'; import * as client from 'openid-client' import { HttpContext } from '@adonisjs/core/http' +import env from '#start/env' async function createConfig() { return await client.discovery( - new URL(process.env.OIDC_DISCOVERY_ENDPOINT), - process.env.OIDC_CLIENT_ID, - process.env.OIDC_CLIENT_SECRET, + new URL(env.get('OIDC_DISCOVERY_ENDPOINT')), + env.get('OIDC_CLIENT_ID'), + env.get('OIDC_CLIENT_SECRET'), undefined, { execute: process.env.NODE_ENV === "development" ? [client.allowInsecureRequests] : [] @@ -81,9 +82,13 @@ export default class OIDCController { await auth.use('web').login(user); + if(!tokens.expires_in || !tokens.refresh_expires_in) { + return response.abort("Expiration parameter not found in tokens given by oidc provider", 500); + } + return response .cookie('access_token', tokens.access_token, { expires: new Date((new Date()).getTime() + (tokens.expires_in)) }) - .cookie('refresh_token', tokens.refresh_token, { expires: new Date((new Date()).getTime() + (tokens.expires_in)) }) + .cookie('refresh_token', tokens.refresh_token, { expires: new Date((new Date()).getTime() + Number((tokens.refresh_expires_in))) }) .redirect() .toPath('/'); } diff --git a/website/app/lib/oidc.ts b/website/app/lib/oidc.ts index 1d6adcf..5517f63 100644 --- a/website/app/lib/oidc.ts +++ b/website/app/lib/oidc.ts @@ -1,5 +1,7 @@ +import env from '#start/env' + export async function oidcRenewTokens(refresh_token: string) { - return await fetch(`${process.env.OIDC_TOKEN_ENDPOINT}`, { + return fetch(`${env.get('OIDC_TOKEN_ENDPOINT')}`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', diff --git a/website/app/middleware/oidc_token_refresher_middleware.ts b/website/app/middleware/oidc_token_refresher_middleware.ts index 23114c7..5e75190 100644 --- a/website/app/middleware/oidc_token_refresher_middleware.ts +++ b/website/app/middleware/oidc_token_refresher_middleware.ts @@ -1,25 +1,38 @@ import type { HttpContext } from '@adonisjs/core/http' import type { NextFn } from '@adonisjs/core/types/http' import * as jose from 'jose' +import type { OidcTokenResponse } from '../../types/oidc.ts'; import { oidcRenewTokens } from '../lib/oidc.js'; +import { Exception } from '@adonisjs/core/exceptions'; export default class OidcTokenRefresherMiddleware { async handle(ctx: HttpContext, next: NextFn) { const { request } = ctx; - const refresh_token = jose.decodeJwt(request.cookie("refresh_token")); - const access_token = jose.decodeJwt(request.cookie("access_token")); + const refresh_token_cookie = request.cookie("refresh_token"); + const access_token_cookie = request.cookie("access_token"); + + if(refresh_token_cookie === undefined || access_token_cookie === undefined) { + return await next(); + } + + const refresh_token = jose.decodeJwt(refresh_token_cookie); + const access_token = jose.decodeJwt(access_token_cookie); + + if(!refresh_token.exp || !access_token.exp) { + throw new Exception("Invalid tokens"); + } const refresh_token_expired = new Date(refresh_token.exp * 1000) < new Date(); const access_token_expired = new Date(access_token.exp * 1000) < new Date(); - if ((refresh_token && access_token) && (refresh_token_expired || access_token_expired)) { + if (refresh_token_expired || access_token_expired) { try { - const res = await oidcRenewTokens(refresh_token.refresh_token); + const res = await oidcRenewTokens(refresh_token_cookie); if (res.ok) { - const newTokens = await res.json(); + const newTokens: OidcTokenResponse = await res.json() as OidcTokenResponse; ctx.response .cookie('access_token', newTokens.access_token, { expires: new Date((new Date()).getTime() + (newTokens.expires_in)) }) diff --git a/website/app/models/user.ts b/website/app/models/user.ts index ea9f975..8cd998c 100644 --- a/website/app/models/user.ts +++ b/website/app/models/user.ts @@ -3,7 +3,6 @@ import hash from '@adonisjs/core/services/hash' import { compose } from '@adonisjs/core/helpers' import { BaseModel, column } from '@adonisjs/lucid/orm' import { withAuthFinder } from '@adonisjs/auth/mixins/lucid' -import type { JsonValue } from 'openid-client' const AuthFinder = withAuthFinder(() => hash.use('scrypt'), { uids: ['email'], diff --git a/website/start/env.ts b/website/start/env.ts index 39e4874..2a20ebd 100644 --- a/website/start/env.ts +++ b/website/start/env.ts @@ -17,6 +17,11 @@ export default await Env.create(new URL('../', import.meta.url), { APP_KEY: Env.schema.string(), HOST: Env.schema.string({ format: 'host' }), LOG_LEVEL: Env.schema.string(), + OIDC_REDIRECT_URI: Env.schema.string(), + OIDC_DISCOVERY_ENDPOINT: Env.schema.string(), + OIDC_TOKEN_ENDPOINT: Env.schema.string(), + OIDC_CLIENT_ID: Env.schema.string(), + OIDC_CLIENT_SECRET: Env.schema.string(), /* |---------------------------------------------------------- diff --git a/website/types/oidc.ts b/website/types/oidc.ts new file mode 100644 index 0000000..35d8a66 --- /dev/null +++ b/website/types/oidc.ts @@ -0,0 +1,9 @@ +export type OidcTokenResponse = { + access_token: string; + expires_in: number; + refresh_token: string; + token_type: string; + not_before_policy: number; + session_state: string; + scope: string; +} \ No newline at end of file