diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 62818c6..8e4b44f 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -43,12 +43,15 @@ export default defineNuxtConfig({ clientSecret: 'secret', callbackUrl: '', scope: [ + 'openid', 'email', - 'profile' + 'profile', + // 'address' ] }, config: { - response_type: 'id_token', + debug: true, + response_type: 'code', secret: 'oidc._sessionid', cookie: { loginName: '' }, cookiePrefix: 'oidc._', diff --git a/playground/pages/[...slug].vue b/playground/pages/[...slug].vue new file mode 100644 index 0000000..986a301 --- /dev/null +++ b/playground/pages/[...slug].vue @@ -0,0 +1,11 @@ + + + diff --git a/playground/pages/public.vue b/playground/pages/public.vue index 8393c84..242966f 100644 --- a/playground/pages/public.vue +++ b/playground/pages/public.vue @@ -2,6 +2,7 @@
+

Public Page

You should see this page without need to authentication! diff --git a/playground/pages/secure.vue b/playground/pages/secure.vue index 8393c84..b59463d 100644 --- a/playground/pages/secure.vue +++ b/playground/pages/secure.vue @@ -2,12 +2,18 @@
+

Secure Page

- You should see this page without need to authentication! + Secured content + You should see this page only if authenticated!
- diff --git a/src/module.ts b/src/module.ts index 7b1480f..1c05f8c 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,7 +1,9 @@ import { fileURLToPath } from 'url' -import { defineNuxtModule, addPlugin, resolveModule, createResolver } from '@nuxt/kit' +import { defineNuxtModule, addPlugin, resolveModule, createResolver } from '@nuxt/kit'; import defu from 'defu' import { name, version } from '../package.json' +import { logger } from './runtime/utils/logger'; + export type CookieSerializeOptions = { domain?: string | undefined; @@ -35,6 +37,7 @@ export type Config = { cookieFlags?: { [key: string]: CookieSerializeOptions, } + debug?: boolean | undefined, } export interface ModuleOptions { @@ -61,13 +64,11 @@ export default defineNuxtModule({ clientSecret: '', callbackUrl: '', scope: [ - 'email', - 'profile', - 'address' ] }, // express-session configuration config: { + debug: false, secret: 'oidc._sessionid', // process.env.OIDC_SESSION_SECRET cookie: {}, cookiePrefix: 'oidc._', @@ -80,8 +81,12 @@ export default defineNuxtModule({ cookieFlags: {} } }, - setup (options, nuxt) { - // console.log(options.op) + setup(options, nuxt) { + logger.level = options.config.debug == true ? 5 : 0 // 4 = debug, 5 = trace + logger.info('[DEBUG MODE]: ', options.config.debug); + logger.debug('[WITHOUT ENV VARS] options:', options); + + const { resolve } = createResolver(import.meta.url) const resolveRuntimeModule = (path: string) => resolveModule(path, { paths: resolve('./runtime') }) @@ -144,4 +149,4 @@ export default defineNuxtModule({ addPlugin(resolve(runtimeDir, 'plugin')) } } -}) +}) \ No newline at end of file diff --git a/src/runtime/plugin.ts b/src/runtime/plugin.ts index f009ff0..5ee849a 100644 --- a/src/runtime/plugin.ts +++ b/src/runtime/plugin.ts @@ -3,6 +3,7 @@ import { Storage, StorageOptions } from './storage' import { isUnset, isSet } from './utils/utils' import { encrypt, decrypt } from './utils/encrypt' import { useState, useFetch, useRuntimeConfig, useCookie } from '#imports' +import { logger } from '@nuxt/kit' interface UseState { user: any, @@ -14,7 +15,7 @@ class Oidc { private $useState: any // State: Nuxt.useState (share state in all nuxt pages and components) https://v3.nuxtjs.org/guide/features/state-management public $storage: Storage // LocalStorage: Browser.localStorage (share state in all sites, use in page refresh.) - constructor () { + constructor() { this.state = { user: {}, isLoggedIn: false } this.$useState = useState('useState', () => { return { user: {}, isLoggedIn: false } }) @@ -29,7 +30,7 @@ class Oidc { this.$storage = storage } - get user () { + get user() { const userInfoState = this.$useState.value.user const userInfoLS = this.$storage.getUserInfo() if ((isUnset(userInfoState))) { @@ -42,14 +43,14 @@ class Oidc { // return this.state.user // not auto update } - get isLoggedIn () { + get isLoggedIn() { const isLoggedIn = this.$useState.value.isLoggedIn const isLoggedInLS = this.$storage.isLoggedIn() // console.log('isLoggedIn', isLoggedIn, isLoggedInLS) return isLoggedIn || isLoggedInLS } - setUser (user: any) { + setUser(user: any) { this.state.user = user this.state.isLoggedIn = Object.keys(user).length > 0 @@ -59,7 +60,7 @@ class Oidc { this.$storage.setUserInfo(user) } - async fetchUser () { + async fetchUser() { try { if (process.server) { console.log('serve-render: fetchUser from cookie.') @@ -93,7 +94,7 @@ class Oidc { } } - login (redirect = '/') { + login(redirect = '/') { if (process.client) { const params = new URLSearchParams({ redirect }) const toStr = '/oidc/login' // + params.toString() @@ -102,7 +103,7 @@ class Oidc { } } - logout () { + logout() { // TODO clear user info when accessToken expired. if (process.client) { this.$useState.value.user = {} @@ -115,8 +116,11 @@ class Oidc { } export default defineNuxtPlugin((nuxtApp) => { + // TODO: enable consola debug mode here instead of console.log console.log('--- Nuxt plugin: nuxt-openid-connect!') const oidc = new Oidc() nuxtApp.provide('oidc', oidc) + // console.log('--- Nuxt plugin: DEBUG MODE:' + useNuxtApp().ssrContext?.runtimeConfig.openidConnect.config.debug); + oidc.fetchUser() // render both from server and client. }) diff --git a/src/runtime/server/routes/oidc/callback.ts b/src/runtime/server/routes/oidc/callback.ts index 81b451f..62ed6de 100644 --- a/src/runtime/server/routes/oidc/callback.ts +++ b/src/runtime/server/routes/oidc/callback.ts @@ -2,9 +2,10 @@ import { defineEventHandler, getCookie, setCookie } from 'h3' import { initClient } from '../../../utils/issueclient' import { encrypt } from '../../../utils/encrypt' import { useRuntimeConfig } from '#imports' +import { logger } from '../../../utils/logger' export default defineEventHandler(async (event) => { - console.log('oidc/callback calling') + logger.debug('[CALLBACK]: oidc/callback calling') const { op, config } = useRuntimeConfig().openidConnect const sessionid = getCookie(event, config.secret) const req = event.node.req @@ -14,19 +15,21 @@ export default defineEventHandler(async (event) => { const callBackUrl = op.callbackUrl.replace('cbt', 'callback') if (params.access_token) { + logger.debug('[CALLBACK]: has access_token in params') await getUserInfo(params.access_token) } else if (params.code) { + logger.debug('[CALLBACK]: has code in params') const tokenSet = await issueClient.callback(callBackUrl, params, { nonce: sessionid }) if (tokenSet.access_token) { await getUserInfo(tokenSet.access_token) } } else { - console.log('empty callback') + logger.debug('[CALLBACK]: empty callback') } res.writeHead(302, { Location: '/' }) res.end() - async function getUserInfo (accessToken: string) { + async function getUserInfo(accessToken: string) { try { const userinfo = await issueClient.userinfo(accessToken) setCookie(event, config.cookiePrefix + 'access_token', accessToken, { @@ -45,7 +48,7 @@ export default defineEventHandler(async (event) => { const encryptedText = await encrypt(JSON.stringify(userinfo), config) setCookie(event, config.cookiePrefix + 'user_info', encryptedText, { ...config.cookieFlags['user_info' as keyof typeof config.cookieFlags] }) } catch (err) { - console.log(err) + logger.error("[CALLBACK]: " + err) } } }) diff --git a/src/runtime/server/routes/oidc/cbt.ts b/src/runtime/server/routes/oidc/cbt.ts index 12bc49c..1ec6a5e 100644 --- a/src/runtime/server/routes/oidc/cbt.ts +++ b/src/runtime/server/routes/oidc/cbt.ts @@ -1,16 +1,17 @@ import { defineEventHandler, setCookie, getCookie } from 'h3' import { CBT_PAGE_TEMPATE } from '../../../utils/template' import { useRuntimeConfig } from '#imports' +import { logger } from '@nuxt/kit' export default defineEventHandler((event) => { - console.log('oidc/cbt calling') + logger.debug('[CBT]: oidc/cbt calling') const { config } = useRuntimeConfig().openidConnect const res = event.res const html = CBT_PAGE_TEMPATE const sessionkey = config.secret const sessionid = getCookie(event, sessionkey) - // console.log(sessionid) + // logger.debug('[CBT]:' + sessionid) /* setCookie(event, sessionkey, sessionid, { maxAge: 24 * 60 * 60 // oneday }) */ diff --git a/src/runtime/server/routes/oidc/login.ts b/src/runtime/server/routes/oidc/login.ts index 1715b24..c187949 100644 --- a/src/runtime/server/routes/oidc/login.ts +++ b/src/runtime/server/routes/oidc/login.ts @@ -3,9 +3,10 @@ import { v4 as uuidv4 } from 'uuid' import { initClient } from '../../../utils/issueclient' import { useRuntimeConfig } from '#imports' +import { logger } from '../../../utils/logger' export default defineEventHandler(async (event) => { - console.log('oidc/login calling') + logger.info('[Login]: oidc/login calling') const { op, config } = useRuntimeConfig().openidConnect const req = event.req @@ -14,12 +15,12 @@ export default defineEventHandler(async (event) => { const sessionkey = config.secret let sessionid = getCookie(event, config.secret) if (!sessionid) { - // console.log('regenerate sessionid') + logger.trace('[Login]: regenerate sessionid') sessionid = uuidv4() } const callbackUrl = (op.callbackUrl && op.callbackUrl.length > 0) ? op.callbackUrl : 'http://' + req.headers.host + '/oidc/cbt' - // console.log('cabackurl:', callbackUrl, op.callbackUrl) + logger.trace('[Login]: cabackurl: ', callbackUrl, op.callbackUrl) const parameters = { redirect_uri: callbackUrl, @@ -28,9 +29,9 @@ export default defineEventHandler(async (event) => { scope: op.scope.join(' ') // 'openid' will be added by default } const authUrl = issueClient.authorizationUrl(parameters) - // console.log(authUrl) + logger.trace('[Login]: Auth Url: ' + authUrl) - console.log(sessionid) + logger.debug('[Login]: sessionid: ' + sessionid) if (sessionid) { setCookie(event, sessionkey, sessionid, { maxAge: config.cookieMaxAge, diff --git a/src/runtime/server/routes/oidc/logout.ts b/src/runtime/server/routes/oidc/logout.ts index 12e04e6..99ba9e2 100644 --- a/src/runtime/server/routes/oidc/logout.ts +++ b/src/runtime/server/routes/oidc/logout.ts @@ -1,9 +1,10 @@ import { getCookie, deleteCookie, defineEventHandler } from 'h3' import { useRuntimeConfig } from '#imports' +import { logger } from '../../../utils/logger' export default defineEventHandler((event) => { const res = event.res - console.log('oidc/logout calling') + logger.log('[LOGOUT]: oidc/logout calling') const { config } = useRuntimeConfig().openidConnect deleteCookie(event, config.secret) diff --git a/src/runtime/server/routes/oidc/user.ts b/src/runtime/server/routes/oidc/user.ts index d5c90ee..78ee8aa 100644 --- a/src/runtime/server/routes/oidc/user.ts +++ b/src/runtime/server/routes/oidc/user.ts @@ -2,11 +2,12 @@ import { getCookie, deleteCookie, defineEventHandler } from 'h3' import { initClient } from '../../../utils/issueclient' import { encrypt, decrypt } from '../../../utils/encrypt' import { useRuntimeConfig } from '#imports' +import { logger } from '../../../utils/logger' export default defineEventHandler(async (event) => { const { config, op } = useRuntimeConfig().openidConnect - console.log('oidc/user calling') - // console.log(req.headers.cookie) + logger.debug('[USER]: oidc/user calling') + logger.trace('[USER]: ' + event.req.headers.cookie) const sessionid = getCookie(event, config.secret) const accesstoken = getCookie(event, config.cookiePrefix + 'access_token') @@ -30,7 +31,7 @@ export default defineEventHandler(async (event) => { } return userinfo } catch (err) { - console.log(err) + logger.error('[USER]: ' + err) deleteCookie(event, config.secret) deleteCookie(event, config.cookiePrefix + 'access_token') deleteCookie(event, config.cookiePrefix + 'user_info') @@ -43,7 +44,7 @@ export default defineEventHandler(async (event) => { return {} } } else { - console.log('empty accesstoken for access userinfo') + logger.debug('[USER]: empty accesstoken for access userinfo') return {} } }) diff --git a/src/runtime/storage.ts b/src/runtime/storage.ts index 9280563..b4b3f80 100644 --- a/src/runtime/storage.ts +++ b/src/runtime/storage.ts @@ -1,28 +1,28 @@ import { isUnset, isSet } from './utils/utils' export type StorageOptions = { - localStorage: boolean, - prefix: string, - ignoreExceptions: boolean + localStorage: boolean, + prefix: string, + ignoreExceptions: boolean } export class Storage { public options: StorageOptions private userInfoKey: string - constructor (options: StorageOptions) { + constructor(options: StorageOptions) { this.options = options this.userInfoKey = 'user' } - getPrefix (): string { + getPrefix(): string { if (!this.options.localStorage) { throw new Error('Cannot get prefix; localStorage is off') } return this.options.prefix } - setUserInfo (user:any) { + setUserInfo(user: any) { if (isUnset(user)) { return } @@ -35,7 +35,7 @@ export class Storage { this.setLocalStorage(this.userInfoKey, _userValue) } - getUserInfo (): any { + getUserInfo(): any { if (this.isLocalStorageEnabled()) { const _user = this.getLocalStorage(this.userInfoKey) try { @@ -48,18 +48,18 @@ export class Storage { } } - removeUserInfo (): void { + removeUserInfo(): void { if (this.isLocalStorageEnabled()) { this.removeLocalStorage(this.userInfoKey) } } - isLoggedIn (): boolean { + isLoggedIn(): boolean { const user = this.getUserInfo() return isSet(user) && Object.keys(user).length > 0 } - setLocalStorage (key: string, value: string): string | void { + setLocalStorage(key: string, value: string): string | void { // Unset null, undefined if (isUnset(value)) { return this.removeLocalStorage(key) @@ -82,7 +82,7 @@ export class Storage { return value } - getLocalStorage (key: string): string { + getLocalStorage(key: string): string { if (!this.isLocalStorageEnabled()) { return } @@ -94,7 +94,7 @@ export class Storage { return value } - removeLocalStorage (key: string): void { + removeLocalStorage(key: string): void { if (!this.isLocalStorageEnabled()) { return } @@ -104,7 +104,7 @@ export class Storage { } // origin from https://github.com/nuxt-community/auth-module - isLocalStorageEnabled (): boolean { + isLocalStorageEnabled(): boolean { // Local Storage only exists in the browser if (!process.client) { return false @@ -123,7 +123,7 @@ export class Storage { // eslint-disable-next-line no-console console.warn( "[AUTH] Local storage is enabled in config, but browser doesn't" + - ' support it' + ' support it' ) } return false diff --git a/src/runtime/utils/issueclient.ts b/src/runtime/utils/issueclient.ts index f2ae16c..e2d21ad 100644 --- a/src/runtime/utils/issueclient.ts +++ b/src/runtime/utils/issueclient.ts @@ -1,6 +1,8 @@ import { Issuer } from 'openid-client' import { OidcProvider } from '../../module' import { useRuntimeConfig } from '#imports' +import { logger } from './logger' + export const initClient = async (op: OidcProvider, req: any) => { const { config } = useRuntimeConfig().openidConnect @@ -8,7 +10,7 @@ export const initClient = async (op: OidcProvider, req: any) => { const callbackUrl = host ? 'http://' + host + '/oidc/cbt' : op.callbackUrl const issuer = await Issuer.discover(op.issuer) - // console.log('Discovered issuer %s %O', issuer.issuer, issuer.metadata) + logger.trace('Discovered issuer %s %O', issuer.issuer, issuer.metadata) const client = new issuer.Client({ client_id: op.clientId, client_secret: op.clientSecret, diff --git a/src/runtime/utils/logger.ts b/src/runtime/utils/logger.ts new file mode 100644 index 0000000..ca3367e --- /dev/null +++ b/src/runtime/utils/logger.ts @@ -0,0 +1,20 @@ +import { logger as initLogger } from "@nuxt/kit" + +const getLevel = (): number => { // load + try { + return useRuntimeConfig().openidConnect.config.debug ? 4 : 0 // 5: trace, 4: debug, 0: errors + } catch (error) { + return 0 + } +} + +// Note: tag not working after nuxt ready +const logger = initLogger.create({ + // level: 5, + defaults: { + tag: 'nuxt-opentid', + }, + level: getLevel(), +}) + +export { logger } \ No newline at end of file diff --git a/src/runtime/utils/template.ts b/src/runtime/utils/template.ts index 2e21749..aaec716 100644 --- a/src/runtime/utils/template.ts +++ b/src/runtime/utils/template.ts @@ -1,3 +1,4 @@ +const debug = useRuntimeConfig().openidConnect.config.debug ?? false export const CBT_PAGE_TEMPATE = ` @@ -8,7 +9,7 @@ export const CBT_PAGE_TEMPATE = `