├── @nuxtjs-alt ├── http │ ├── tsconfig.json │ ├── .gitignore │ ├── playground │ │ ├── app.vue │ │ └── nuxt.config.ts │ ├── src │ │ ├── runtime │ │ │ ├── templates │ │ │ │ ├── interceptor.plugin.mjs │ │ │ │ └── http.plugin.mjs │ │ │ └── composables.ts │ │ ├── types.ts │ │ └── module.ts │ ├── package.json │ ├── build.config.ts │ └── readme.md ├── proxy │ ├── tsconfig.json │ ├── playground │ │ ├── package.json │ │ ├── app.vue │ │ └── nuxt.config.ts │ ├── .gitignore │ ├── src │ │ ├── runtime │ │ │ ├── fetch.interceptor.mjs │ │ │ └── interceptor.mjs │ │ ├── types.ts │ │ └── module.ts │ ├── package.json │ ├── readme.md │ └── build.config.ts ├── auth │ ├── tsconfig.json │ ├── playground │ │ ├── package.json │ │ ├── app.vue │ │ └── nuxt.config.ts │ ├── src │ │ ├── runtime │ │ │ ├── index.ts │ │ │ ├── core │ │ │ │ ├── index.ts │ │ │ │ └── middleware.ts │ │ │ ├── composables.ts │ │ │ ├── schemes │ │ │ │ ├── index.ts │ │ │ │ ├── laravel-jwt.ts │ │ │ │ ├── base.ts │ │ │ │ ├── auth0.ts │ │ │ │ ├── cookie.ts │ │ │ │ ├── refresh.ts │ │ │ │ ├── local.ts │ │ │ │ └── openIDConnect.ts │ │ │ └── inc │ │ │ │ ├── expired-auth-session-error.ts │ │ │ │ ├── configuration-document-request-error.ts │ │ │ │ ├── index.ts │ │ │ │ ├── refresh-controller.ts │ │ │ │ ├── token-status.ts │ │ │ │ ├── refresh-token.ts │ │ │ │ ├── id-token.ts │ │ │ │ ├── token.ts │ │ │ │ ├── request-handler.ts │ │ │ │ └── configuration-document.ts │ │ ├── types │ │ │ ├── router.ts │ │ │ ├── request.ts │ │ │ ├── request.d.ts │ │ │ ├── router.d.ts │ │ │ ├── utils.ts │ │ │ ├── utils.d.ts │ │ │ ├── provider.d.ts │ │ │ ├── provider.ts │ │ │ ├── strategy.ts │ │ │ ├── strategy.d.ts │ │ │ ├── index.d.ts │ │ │ ├── index.ts │ │ │ ├── openIDConnectConfigurationDocument.ts │ │ │ ├── options.d.ts │ │ │ ├── options.ts │ │ │ ├── openIDConnectConfigurationDocument.d.ts │ │ │ ├── scheme.ts │ │ │ └── scheme.d.ts │ │ ├── providers │ │ │ ├── index.ts │ │ │ ├── google.ts │ │ │ ├── facebook.ts │ │ │ ├── github.ts │ │ │ ├── auth0.ts │ │ │ ├── discord.ts │ │ │ ├── laravel-sanctum.ts │ │ │ ├── laravel-jwt.ts │ │ │ └── laravel-passport.ts │ │ ├── options.ts │ │ ├── plugin.ts │ │ ├── module.ts │ │ ├── resolve.ts │ │ └── utils │ │ │ └── index.ts │ ├── .gitignore │ ├── .editorconfig │ ├── package.json │ ├── build.config.ts │ └── readme.md ├── axios │ ├── tsconfig.json │ ├── readme.md │ ├── .gitignore │ ├── playground │ │ ├── app.vue │ │ └── nuxt.config.ts │ ├── package.json │ ├── build.config.ts │ └── src │ │ ├── types.ts │ │ ├── module.d.ts │ │ ├── options.ts │ │ ├── module.ts │ │ └── runtime │ │ └── templates │ │ └── axios.plugin.mjs └── vuetify │ ├── tsconfig.json │ ├── .gitignore │ ├── playground │ ├── app.vue │ └── nuxt.config.ts │ ├── src │ ├── runtime │ │ ├── templates │ │ │ └── plugin.mjs │ │ └── vuetify.ts │ ├── types.ts │ └── module.ts │ ├── package.json │ ├── readme.md │ └── build.config.ts ├── .github ├── stale.yml └── ISSUE_TEMPLATE │ └── bug-report.yml ├── LICENSE └── README.md /@nuxtjs-alt/http/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json", 3 | } 4 | -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json", 3 | } 4 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@nuxtjs-alt/auth" 4 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@nuxtjs-alt/proxy" 4 | } 5 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./core"; 2 | export * from "./inc"; 3 | export * from "./schemes"; 4 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | export * from './middleware'; 3 | export * from './storage'; 4 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/router.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocationNormalized } from 'vue-router' 2 | 3 | export type Route = RouteLocationNormalized; 4 | 5 | -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/readme.md: -------------------------------------------------------------------------------- 1 | **Information** 2 | 3 | This serves as an alternative to the @nuxtjs/axios module for nuxt. Please note this is only for nuxt3. -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/composables.ts: -------------------------------------------------------------------------------- 1 | import type { Auth } from './core' 2 | import { useNuxtApp } from '#imports' 3 | 4 | export const useAuth = (): Auth => useNuxtApp().$auth -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_STORE 8 | coverage 9 | dist 10 | package-lock.json 11 | temp 12 | tsdoc-metadata.json -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_STORE 8 | coverage 9 | dist 10 | package-lock.json 11 | temp 12 | tsdoc-metadata.json -------------------------------------------------------------------------------- /@nuxtjs-alt/http/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_STORE 8 | coverage 9 | dist 10 | package-lock.json 11 | temp 12 | tsdoc-metadata.json -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_STORE 8 | coverage 9 | dist 10 | package-lock.json 11 | temp 12 | tsdoc-metadata.json -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_STORE 8 | coverage 9 | dist 10 | package-lock.json 11 | temp 12 | tsdoc-metadata.json -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/request.ts: -------------------------------------------------------------------------------- 1 | import { FetchConfig } from '@refactorjs/ofetch' 2 | 3 | export type HTTPRequest = FetchConfig & { 4 | 5 | }; 6 | export type HTTPResponse = Promise; 7 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/request.d.ts: -------------------------------------------------------------------------------- 1 | import { FetchConfig } from '@refactorjs/ofetch' 2 | 3 | export type HTTPRequest = FetchConfig & { 4 | 5 | }; 6 | export type HTTPResponse = Promise; 7 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /@nuxtjs-alt/http/playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from "nuxt"; 2 | import MyModule from '..' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [ 6 | MyModule 7 | ], 8 | vuetify: {}, 9 | }); 10 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/router.d.ts: -------------------------------------------------------------------------------- 1 | export interface VueComponent { 2 | options: object; 3 | _Ctor: VueComponent; 4 | } 5 | 6 | export type MatchedRoute = { components: VueComponent[] }; 7 | 8 | export type Route = { matched: MatchedRoute[] }; 9 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/utils.ts: -------------------------------------------------------------------------------- 1 | export type RecursivePartial = { 2 | [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial[any] : RecursivePartial; 3 | }; 4 | 5 | export type PartialExcept = RecursivePartial &Pick; 6 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/utils.d.ts: -------------------------------------------------------------------------------- 1 | export type RecursivePartial = { 2 | [P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial[any] : RecursivePartial; 3 | }; 4 | 5 | export type PartialExcept = RecursivePartial &Pick; 6 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 4 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/schemes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './base'; 2 | export * from './cookie'; 3 | export * from './local'; 4 | export * from './oauth2'; 5 | export * from './openIDConnect'; 6 | export * from './refresh'; 7 | export * from './auth0'; 8 | export * from './laravel-jwt'; 9 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/playground/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/expired-auth-session-error.ts: -------------------------------------------------------------------------------- 1 | export class ExpiredAuthSessionError extends Error { 2 | constructor() { 3 | super('Both token and refresh token have expired. Your request was aborted.'); 4 | this.name = 'ExpiredAuthSessionError'; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /@nuxtjs-alt/http/playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import httpModule from '..' 2 | import { defineNuxtConfig } from 'nuxt/config' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [ 6 | httpModule, 7 | ], 8 | http: { 9 | interceptorPlugin: true 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/configuration-document-request-error.ts: -------------------------------------------------------------------------------- 1 | export class ConfigurationDocumentRequestError extends Error { 2 | constructor() { 3 | super('Error loading OpenIDConnect configuration document'); 4 | this.name = 'ConfigurationDocumentRequestError'; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import Module from '..' 2 | 3 | export default defineNuxtConfig({ 4 | modules: [ 5 | Module, 6 | "@nuxtjs-alt/http", 7 | "@pinia/nuxt", 8 | ], 9 | auth: { 10 | enableMiddleware: false, 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import ProxyModule from '..' 2 | import { defineNuxtConfig } from 'nuxt/config' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [ 6 | ProxyModule 7 | ], 8 | proxy: { 9 | proxies: { 10 | '/api': 'http://localhost:3001' 11 | } 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/index.ts: -------------------------------------------------------------------------------- 1 | export * from './configuration-document-request-error'; 2 | export * from './configuration-document'; 3 | export * from './expired-auth-session-error'; 4 | export * from './refresh-controller'; 5 | export * from './refresh-token'; 6 | export * from './request-handler'; 7 | export * from './token-status'; 8 | export * from './token'; 9 | export * from './id-token'; 10 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/schemes/laravel-jwt.ts: -------------------------------------------------------------------------------- 1 | import type { HTTPResponse } from '../../types'; 2 | import { RefreshScheme } from './refresh'; 3 | 4 | export class LaravelJWTScheme extends RefreshScheme { 5 | protected updateTokens(response: HTTPResponse, { isRefreshing = false, updateOnRefresh = false } = {}): void { 6 | super.updateTokens(response, { isRefreshing, updateOnRefresh }); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from "nuxt"; 2 | import axiosModule from '..' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [ 6 | axiosModule 7 | ], 8 | vite: { 9 | server: { 10 | hmr: { 11 | clientPort: 443, 12 | path: "hmr/", 13 | }, 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/src/runtime/templates/plugin.mjs: -------------------------------------------------------------------------------- 1 | import { defineNuxtPlugin } from '#imports'; 2 | import { createVuetify } from '#vuetify'; 3 | 4 | const options = JSON.parse('<%= JSON.stringify(options) %>') 5 | 6 | export default defineNuxtPlugin(nuxtApp => { 7 | const vuetify = createVuetify(options) 8 | nuxtApp.vueApp.use(vuetify) 9 | 10 | return { 11 | provide: { 12 | vuetify 13 | } 14 | } 15 | }) -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth0'; 2 | export * from './discord'; 3 | export * from './facebook'; 4 | export * from './github'; 5 | export * from './google'; 6 | export * from './laravel-jwt'; 7 | export * from './laravel-passport'; 8 | export * from './laravel-sanctum'; 9 | 10 | export const ProviderAliases = { 11 | 'laravel/jwt': 'laravelJWT', 12 | 'laravel/passport': 'laravelPassport', 13 | 'laravel/sanctum': 'laravelSanctum', 14 | }; 15 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/provider.d.ts: -------------------------------------------------------------------------------- 1 | import type { SchemeOptions } from "./scheme"; 2 | import type { PartialExcept } from "./utils"; 3 | 4 | export interface ProviderOptions { 5 | scheme: string; 6 | clientSecret: string | number; 7 | } 8 | 9 | export type ProviderOptionsKeys = Exclude; 10 | 11 | export type ProviderPartialOptions = PartialExcept; 12 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/provider.ts: -------------------------------------------------------------------------------- 1 | import type { SchemeOptions } from './scheme'; 2 | import type { PartialExcept } from './utils'; 3 | 4 | export interface ProviderOptions { 5 | scheme: string; 6 | clientSecret: string | number; 7 | } 8 | 9 | export type ProviderOptionsKeys = Exclude; 10 | 11 | export type ProviderPartialOptions = PartialExcept; 12 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/strategy.ts: -------------------------------------------------------------------------------- 1 | import type { SchemeOptions } from './scheme'; 2 | import type { ProviderPartialOptions, ProviderOptions } from './provider'; 3 | 4 | export interface Strategy extends SchemeOptions { 5 | provider?: string | ((...args: any[]) => any); 6 | scheme?: string; 7 | enabled?: boolean; 8 | [option: string]: any; 9 | } 10 | 11 | export type StrategyOptions = ProviderPartialOptions; 12 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/strategy.d.ts: -------------------------------------------------------------------------------- 1 | import type { SchemeOptions } from "./scheme"; 2 | import type { ProviderPartialOptions, ProviderOptions } from "./provider"; 3 | 4 | export interface Strategy extends SchemeOptions { 5 | provider?: string | ((...args: any[]) => any); 6 | scheme?: string; 7 | enabled?: boolean; 8 | [option: string]: any; 9 | } 10 | 11 | export type StrategyOptions = ProviderPartialOptions; 12 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/schemes/base.ts: -------------------------------------------------------------------------------- 1 | import type { SchemeOptions } from '../../types'; 2 | import type { Auth } from '..'; 3 | import { defu } from 'defu'; 4 | 5 | export class BaseScheme { 6 | options: OptionsT; 7 | 8 | constructor(public $auth: Auth, ...options: OptionsT[]) { 9 | this.options = options.reduce((p, c) => defu(p, c), {}) as OptionsT; 10 | } 11 | 12 | get name(): string { 13 | return this.options.name as string; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/schemes/auth0.ts: -------------------------------------------------------------------------------- 1 | import { withQuery } from 'ufo'; 2 | import { Oauth2Scheme } from '../schemes/oauth2'; 3 | 4 | export class Auth0Scheme extends Oauth2Scheme { 5 | logout(): void { 6 | this.$auth.reset(); 7 | 8 | const opts = { 9 | client_id: this.options.clientId as string, 10 | returnTo: this.logoutRedirectURI, 11 | }; 12 | 13 | const url = withQuery(this.options.endpoints.logout as string, opts) 14 | window.location.replace(url); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleOptions } from "./options"; 2 | import * as NuxtSchema from '@nuxt/schema'; 3 | 4 | export * from "./openIDConnectConfigurationDocument"; 5 | export * from "./provider"; 6 | export * from "./request"; 7 | export * from "./router"; 8 | export * from "./scheme"; 9 | export * from "./strategy"; 10 | export * from "./utils"; 11 | export * from "./options"; 12 | 13 | declare const AuthModule: NuxtSchema.NuxtModule; 14 | 15 | declare module "@nuxt/schema" { 16 | export interface NuxtConfig { 17 | ["auth"]?: Partial; 18 | } 19 | export interface NuxtOptions { 20 | ["auth"]?: ModuleOptions; 21 | } 22 | } 23 | 24 | export default AuthModule -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 3 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 5 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/google.ts: -------------------------------------------------------------------------------- 1 | import type { ProviderPartialOptions, ProviderOptions } from '../types'; 2 | import type { Oauth2SchemeOptions } from '../runtime'; 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { assignDefaults } from '../utils/provider'; 5 | 6 | export interface GoogleProviderOptions extends ProviderOptions, Oauth2SchemeOptions {} 7 | 8 | export function google(nuxt: Nuxt, strategy: ProviderPartialOptions): void { 9 | const DEFAULTS: typeof strategy = { 10 | scheme: 'oauth2', 11 | endpoints: { 12 | authorization: 'https://accounts.google.com/o/oauth2/auth', 13 | userInfo: 'https://www.googleapis.com/oauth2/v3/userinfo', 14 | }, 15 | scope: ['openid', 'profile', 'email'], 16 | }; 17 | 18 | assignDefaults(strategy, DEFAULTS); 19 | } 20 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/facebook.ts: -------------------------------------------------------------------------------- 1 | import type { ProviderPartialOptions, ProviderOptions } from '../types'; 2 | import type { Oauth2SchemeOptions } from '../runtime'; 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { assignDefaults } from '../utils/provider'; 5 | 6 | export interface FacebookProviderOptions extends ProviderOptions, Oauth2SchemeOptions {} 7 | 8 | export function facebook(nuxt: Nuxt, strategy: ProviderPartialOptions): void { 9 | const DEFAULTS: typeof strategy = { 10 | scheme: 'oauth2', 11 | endpoints: { 12 | authorization: 'https://facebook.com/v2.12/dialog/oauth', 13 | userInfo: 'https://graph.facebook.com/v2.12/me?fields=about,name,picture{url},email', 14 | }, 15 | scope: ['public_profile', 'email'], 16 | }; 17 | 18 | assignDefaults(strategy, DEFAULTS); 19 | } 20 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/src/types.ts: -------------------------------------------------------------------------------- 1 | import * as NuxtSchema from '@nuxt/schema'; 2 | import type { VuetifyOptions } from 'vuetify'; 3 | 4 | interface Options { 5 | autoImport?: boolean; 6 | styles?: true | 'none' | 'expose' | 'sass' | { 7 | configFile: string; 8 | }; 9 | /** @internal Only for testing */ 10 | stylesTimeout?: number; 11 | } 12 | 13 | export interface ModuleOptions { 14 | vuetifyOptions?: VuetifyOptions 15 | pluginOptions?: Options 16 | } 17 | 18 | declare module '#app' { 19 | interface NuxtApp { 20 | $vuetify: VuetifyOptions; 21 | } 22 | } 23 | 24 | declare module '@nuxt/schema' { 25 | interface NuxtConfig { 26 | vuetify?: ModuleOptions; 27 | } 28 | interface NuxtOptions { 29 | vuetify?: ModuleOptions 30 | } 31 | } 32 | 33 | declare const NuxtVuetify: NuxtSchema.NuxtModule 34 | 35 | export default NuxtVuetify -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/github.ts: -------------------------------------------------------------------------------- 1 | import type { ProviderOptions, ProviderPartialOptions } from '../types'; 2 | import type { Oauth2SchemeOptions } from '../runtime'; 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { assignDefaults, addAuthorize } from '../utils/provider'; 5 | 6 | export interface GithubProviderOptions extends ProviderOptions, Oauth2SchemeOptions {} 7 | 8 | export function github(nuxt: Nuxt, strategy: ProviderPartialOptions): void { 9 | const DEFAULTS: typeof strategy = { 10 | scheme: 'oauth2', 11 | endpoints: { 12 | authorization: 'https://github.com/login/oauth/authorize', 13 | token: 'https://github.com/login/oauth/access_token', 14 | userInfo: 'https://api.github.com/user', 15 | }, 16 | scope: ['user', 'email'], 17 | }; 18 | 19 | assignDefaults(strategy, DEFAULTS); 20 | 21 | addAuthorize(nuxt, strategy); 22 | } 23 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/auth0.ts: -------------------------------------------------------------------------------- 1 | import type { ProviderOptions, ProviderPartialOptions } from '../types'; 2 | import type { Oauth2SchemeOptions } from '../runtime'; 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { assignDefaults } from '../utils/provider'; 5 | 6 | export interface Auth0ProviderOptions extends ProviderOptions, Oauth2SchemeOptions { 7 | domain: string; 8 | } 9 | 10 | export function auth0(nuxt: Nuxt, strategy: ProviderPartialOptions): void { 11 | const DEFAULTS: typeof strategy = { 12 | scheme: 'auth0', 13 | endpoints: { 14 | authorization: `https://${strategy.domain}/authorize`, 15 | userInfo: `https://${strategy.domain}/userinfo`, 16 | token: `https://${strategy.domain}/oauth/token`, 17 | logout: `https://${strategy.domain}/v2/logout`, 18 | }, 19 | scope: ['openid', 'profile', 'email'], 20 | }; 21 | 22 | assignDefaults(strategy, DEFAULTS); 23 | } 24 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleOptions } from './options'; 2 | import type { Auth } from '../runtime'; 3 | import * as NuxtSchema from '@nuxt/schema'; 4 | 5 | export * from './openIDConnectConfigurationDocument'; 6 | export * from './provider'; 7 | export * from './request'; 8 | export * from './router'; 9 | export * from './scheme'; 10 | export * from './strategy'; 11 | export * from './utils'; 12 | export * from './options'; 13 | 14 | declare module '#app' { 15 | interface NuxtApp extends AuthPluginInjection {} 16 | } 17 | 18 | interface AuthPluginInjection { 19 | $auth: Auth; 20 | } 21 | 22 | declare module '@nuxt/schema' { 23 | export interface NuxtConfig { 24 | auth?: Partial; 25 | } 26 | export interface NuxtOptions { 27 | auth?: Partial; 28 | } 29 | export interface RuntimeConfig { 30 | auth?: Partial 31 | } 32 | } 33 | 34 | declare const NuxtAuth: NuxtSchema.NuxtModule> 35 | 36 | export default NuxtAuth -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Teranode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/discord.ts: -------------------------------------------------------------------------------- 1 | import type { ProviderOptions, ProviderPartialOptions } from '../types'; 2 | import type { Oauth2SchemeOptions } from '../runtime'; 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { assignDefaults, addAuthorize } from '../utils/provider'; 5 | 6 | export interface DiscordProviderOptions extends ProviderOptions, Oauth2SchemeOptions {} 7 | 8 | export function discord(nuxt: Nuxt, strategy: ProviderPartialOptions): void { 9 | const DEFAULTS: typeof strategy = { 10 | scheme: 'oauth2', 11 | endpoints: { 12 | authorization: 'https://discord.com/api/oauth2/authorize', 13 | token: 'https://discord.com/api/oauth2/token', 14 | userInfo: 'https://discord.com/api/users/@me', 15 | // logout: 'https://discord.com/api/oauth2/token/revoke' //TODO: add post method, because discord using the post method to logout 16 | }, 17 | grantType: 'authorization_code', 18 | codeChallengeMethod: 'S256', 19 | scope: ['identify', 'email'], 20 | }; 21 | 22 | assignDefaults(strategy, DEFAULTS); 23 | 24 | addAuthorize(nuxt, strategy, true); 25 | } 26 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/options.ts: -------------------------------------------------------------------------------- 1 | export const moduleDefaults = { 2 | // -- Enable Global Middleware -- 3 | globalMiddleware: false, 4 | 5 | enableMiddleware: true, 6 | 7 | // -- Error handling -- 8 | 9 | resetOnError: false, 10 | 11 | ignoreExceptions: false, 12 | 13 | // -- Authorization -- 14 | 15 | scopeKey: 'scope', 16 | 17 | // -- Redirects -- 18 | 19 | rewriteRedirects: true, 20 | 21 | fullPathRedirect: false, 22 | 23 | watchLoggedIn: true, 24 | 25 | redirect: { 26 | login: '/login', 27 | logout: '/', 28 | home: '/', 29 | callback: '/login', 30 | }, 31 | 32 | // -- Pinia Store -- 33 | 34 | pinia: { 35 | namespace: 'auth', 36 | }, 37 | 38 | // -- Cookie Store -- 39 | 40 | cookie: { 41 | prefix: 'auth.', 42 | options: { 43 | path: '/', 44 | }, 45 | }, 46 | 47 | // -- localStorage Store -- 48 | 49 | localStorage: { 50 | prefix: 'auth.', 51 | }, 52 | 53 | // -- sessionStorage Store -- 54 | 55 | sessionStorage: { 56 | prefix: 'auth.', 57 | }, 58 | 59 | // -- Strategies -- 60 | 61 | defaultStrategy: undefined /* will be auto set at module level */, 62 | 63 | strategies: {}, 64 | }; 65 | -------------------------------------------------------------------------------- /@nuxtjs-alt/http/src/runtime/templates/interceptor.plugin.mjs: -------------------------------------------------------------------------------- 1 | import { defineNuxtPlugin, useRuntimeConfig } from '#imports' 2 | 3 | // Nuxt Options 4 | const proxies = {} 5 | const options = JSON.parse('<%= JSON.stringify(options) %>') 6 | 7 | function isObject(value) { 8 | return Object.prototype.toString.call(value) === '[object Object]' 9 | } 10 | 11 | function doesProxyContextMatchUrl(context, url) { 12 | return ( 13 | (context.startsWith('^') && new RegExp(context).test(url)) || url.startsWith(context) 14 | ) 15 | } 16 | 17 | export default defineNuxtPlugin(({ $http }) => { 18 | Object.keys(options.proxies).forEach((context) => { 19 | let opts = options.proxies[context] 20 | 21 | if (typeof opts === 'string') { 22 | opts = { target: opts } 23 | } 24 | 25 | if (isObject(opts)) { 26 | opts = { ...opts } 27 | } 28 | 29 | proxies[context] = [{ ...opts }] 30 | }) 31 | 32 | if (process.client) { 33 | $http.interceptors.request.use(config => { 34 | for (const context in proxies) { 35 | const [opts] = proxies[context] 36 | if (doesProxyContextMatchUrl(context, config.url)) { 37 | config.baseURL = opts.target 38 | } 39 | } 40 | 41 | return config 42 | }) 43 | } 44 | }) -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/src/runtime/fetch.interceptor.mjs: -------------------------------------------------------------------------------- 1 | import { defineNuxtPlugin } from '#imports' 2 | 3 | // Nuxt Options 4 | const proxies = {} 5 | const options = JSON.parse('<%= JSON.stringify(options) %>') 6 | 7 | function isObject(value) { 8 | return Object.prototype.toString.call(value) === '[object Object]' 9 | } 10 | 11 | function doesProxyContextMatchUrl(context, url) { 12 | return ( 13 | (context.startsWith('^') && new RegExp(context).test(url)) || url.startsWith(context) 14 | ) 15 | } 16 | 17 | export default defineNuxtPlugin(nuxtApp => { 18 | Object.keys(options.proxies).forEach((context) => { 19 | let opts = options.proxies[context] 20 | 21 | if (typeof opts === 'string') { 22 | opts = { target: opts } 23 | } 24 | 25 | if (isObject(opts)) { 26 | opts = { ...opts } 27 | } 28 | 29 | proxies[context] = [{ ...opts }] 30 | }) 31 | 32 | if (process.client && options.fetch) { 33 | $fetch.create({ 34 | async onRequest({ request, options }) { 35 | for (const context in proxies) { 36 | const [opts] = proxies[context] 37 | if (doesProxyContextMatchUrl(context, request)) { 38 | options.baseURL = opts.target 39 | } 40 | } 41 | } 42 | }) 43 | } 44 | }) -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/src/runtime/interceptor.mjs: -------------------------------------------------------------------------------- 1 | import { defineNuxtPlugin, useRuntimeConfig } from '#imports' 2 | 3 | // Nuxt Options 4 | const proxies = {} 5 | const options = JSON.parse('<%= JSON.stringify(options) %>') 6 | 7 | function isObject(value) { 8 | return Object.prototype.toString.call(value) === '[object Object]' 9 | } 10 | 11 | function doesProxyContextMatchUrl(context, url) { 12 | return ( 13 | (context.startsWith('^') && new RegExp(context).test(url)) || url.startsWith(context) 14 | ) 15 | } 16 | 17 | export default defineNuxtPlugin(nuxtApp => { 18 | Object.keys(options.proxies).forEach((context) => { 19 | let opts = options.proxies[context] 20 | 21 | if (typeof opts === 'string') { 22 | opts = { target: opts } 23 | } 24 | 25 | if (isObject(opts)) { 26 | opts = { ...opts } 27 | } 28 | 29 | proxies[context] = [{ ...opts }] 30 | }) 31 | 32 | if (process.client && options.fetch) { 33 | $fetch.create({ 34 | async onRequest({ request, options }) { 35 | for (const context in proxies) { 36 | const [opts] = proxies[context] 37 | if (doesProxyContextMatchUrl(context, request)) { 38 | options.baseURL = opts.target 39 | } 40 | } 41 | } 42 | }) 43 | } 44 | }) -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/openIDConnectConfigurationDocument.ts: -------------------------------------------------------------------------------- 1 | export type OpenIDConnectConfigurationDocument = { 2 | issuer?: string; 3 | authorization_endpoint?: string; 4 | token_endpoint?: string; 5 | token_endpoint_auth_methods_supported?: string[]; 6 | token_endpoint_auth_signing_alg_values_supported?: string[]; 7 | userinfo_endpoint?: string; 8 | check_session_iframe?: string; 9 | end_session_endpoint?: string; 10 | jwks_uri?: string; 11 | registration_endpoint?: string; 12 | scopes_supported?: string[]; 13 | response_types_supported?: string[]; 14 | acr_values_supported?: string[]; 15 | response_modes_supported?: string[]; 16 | grant_types_supported?: string[]; 17 | subject_types_supported?: string[]; 18 | userinfo_signing_alg_values_supported?: string[]; 19 | userinfo_encryption_alg_values_supported?: string[]; 20 | userinfo_encryption_enc_values_supported?: string[]; 21 | id_token_signing_alg_values_supported?: string[]; 22 | id_token_encryption_alg_values_supported?: string[]; 23 | id_token_encryption_enc_values_supported?: string[]; 24 | request_object_signing_alg_values_supported?: string[]; 25 | display_values_supported?: string[]; 26 | claim_types_supported?: string[]; 27 | claims_supported?: string[]; 28 | claims_parameter_supported?: boolean; 29 | service_documentation?: string; 30 | ui_locales_supported?: string[]; 31 | }; 32 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/options.d.ts: -------------------------------------------------------------------------------- 1 | import type { Strategy } from "./strategy"; 2 | import type { NuxtPlugin } from '@nuxt/schema' 3 | 4 | export interface ModuleOptions { 5 | globalMiddleware?: boolean; 6 | enableMiddleware?: boolean; 7 | plugins?: (NuxtPlugin | string)[]; 8 | strategies?: { 9 | [strategy: string]: Strategy | false; 10 | }; 11 | ignoreExceptions: boolean; 12 | resetOnError: boolean | ((...args: any[]) => boolean); 13 | defaultStrategy: string | undefined; 14 | watchLoggedIn: boolean; 15 | rewriteRedirects: boolean; 16 | fullPathRedirect: boolean; 17 | scopeKey: string; 18 | redirect: { 19 | login: string; 20 | logout: string; 21 | callback: string; 22 | home: string; 23 | }; 24 | pinia: { 25 | namespace: string; 26 | }; 27 | cookie: 28 | | { 29 | prefix: string; 30 | options: { 31 | path: string; 32 | expires?: number | Date; 33 | maxAge?: number; 34 | domain?: string; 35 | secure?: boolean; 36 | }; 37 | } 38 | | false; 39 | localStorage: { prefix: string; } | false; 40 | sessionStorage: { prefix: string; } | false; 41 | initialState?: { 42 | user: null; 43 | loggedIn: boolean; 44 | [key: string]: any; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/refresh-controller.ts: -------------------------------------------------------------------------------- 1 | import type { RefreshableScheme, HTTPResponse } from '../../types'; 2 | import type { Auth } from '../core'; 3 | 4 | export class RefreshController { 5 | $auth: Auth; 6 | #refreshPromise: Promise | null = null; 7 | 8 | constructor(public scheme: RefreshableScheme) { 9 | this.$auth = scheme.$auth; 10 | } 11 | 12 | // Multiple requests will be queued until the first has completed token refresh. 13 | handleRefresh(): Promise { 14 | // Another request has started refreshing the token, wait for it to complete 15 | if (this.#refreshPromise) { 16 | return this.#refreshPromise; 17 | } 18 | 19 | return this.#doRefresh(); 20 | } 21 | 22 | // Returns a promise which is resolved when refresh is completed 23 | // Call this function when you intercept a request with an expired token. 24 | 25 | #doRefresh(): Promise { 26 | this.#refreshPromise = new Promise((resolve, reject) => { 27 | this.scheme.refreshTokens() 28 | .then((response) => { 29 | this.#refreshPromise = null; 30 | resolve(response); 31 | }) 32 | .catch((error) => { 33 | this.#refreshPromise = null; 34 | reject(error); 35 | }); 36 | }); 37 | 38 | return this.#refreshPromise; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/token-status.ts: -------------------------------------------------------------------------------- 1 | export enum TokenStatusEnum { 2 | UNKNOWN = 'UNKNOWN', 3 | VALID = 'VALID', 4 | EXPIRED = 'EXPIRED', 5 | } 6 | 7 | export class TokenStatus { 8 | readonly #status: TokenStatusEnum; 9 | 10 | constructor(token: string | boolean, tokenExpiresAt: number | false) { 11 | this.#status = this.#calculate(token, tokenExpiresAt); 12 | } 13 | 14 | unknown(): boolean { 15 | return TokenStatusEnum.UNKNOWN === this.#status; 16 | } 17 | 18 | valid(): boolean { 19 | return TokenStatusEnum.VALID === this.#status; 20 | } 21 | 22 | expired(): boolean { 23 | return TokenStatusEnum.EXPIRED === this.#status; 24 | } 25 | 26 | #calculate(token: string | boolean, tokenExpiresAt: number | false): TokenStatusEnum { 27 | const now = Date.now(); 28 | 29 | try { 30 | if (!token || !tokenExpiresAt) { 31 | return TokenStatusEnum.UNKNOWN; 32 | } 33 | } catch (error) { 34 | return TokenStatusEnum.UNKNOWN; 35 | } 36 | 37 | // Give us some slack to help the token from expiring between validation and usage 38 | const timeSlackMillis = 500; 39 | tokenExpiresAt -= timeSlackMillis; 40 | 41 | // Token is still valid 42 | if (now < tokenExpiresAt) { 43 | return TokenStatusEnum.VALID; 44 | } 45 | 46 | return TokenStatusEnum.EXPIRED; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /@nuxtjs-alt/http/src/types.ts: -------------------------------------------------------------------------------- 1 | import { FetchConfig, FetchInstance } from '@refactorjs/ofetch' 2 | import * as NuxtSchema from '@nuxt/schema'; 3 | 4 | export interface ModuleOptions extends Omit { 5 | baseURL?: string; 6 | browserBaseURL?: string; 7 | host?: string; 8 | prefix?: string; 9 | proxyHeaders?: boolean; 10 | proxyHeadersIgnore?: string[]; 11 | serverTimeout?: number, 12 | clientTimeout?: number, 13 | port?: string | number; 14 | https?: boolean; 15 | retry?: number; 16 | credentials?: string; 17 | headers?: any; 18 | debug?: boolean; 19 | interceptorPlugin?: boolean; 20 | } 21 | 22 | declare global { 23 | var $http: FetchInstance; 24 | namespace NodeJS { 25 | interface Global { 26 | $http: FetchInstance; 27 | } 28 | } 29 | } 30 | 31 | declare module '#app' { 32 | interface NuxtApp extends HttpPluginInjection {} 33 | } 34 | 35 | interface HttpPluginInjection { 36 | $http: FetchInstance; 37 | } 38 | 39 | declare module '@nuxt/schema' { 40 | interface NuxtConfig { 41 | http?: ModuleOptions 42 | } 43 | interface NuxtOptions { 44 | http?: ModuleOptions 45 | } 46 | interface RuntimeConfig { 47 | http?: ModuleOptions; 48 | } 49 | interface PublicRuntimeConfig { 50 | http?: ModuleOptions 51 | } 52 | } 53 | 54 | declare const NuxtHttp: NuxtSchema.NuxtModule 55 | 56 | export default NuxtHttp -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/options.ts: -------------------------------------------------------------------------------- 1 | import type { Strategy } from './strategy'; 2 | import type { NuxtPlugin } from '@nuxt/schema' 3 | 4 | export interface ModuleOptions { 5 | globalMiddleware?: boolean; 6 | enableMiddleware?: boolean; 7 | plugins?: (NuxtPlugin | string)[]; 8 | strategies?: { 9 | [strategy: string]: Strategy | false; 10 | }; 11 | ignoreExceptions: boolean; 12 | resetOnError: boolean | ((...args: any[]) => boolean); 13 | defaultStrategy: string | undefined; 14 | watchLoggedIn: boolean; 15 | rewriteRedirects: boolean; 16 | fullPathRedirect: boolean; 17 | scopeKey: string; 18 | redirect: { 19 | login: string; 20 | logout: string; 21 | callback: string; 22 | home: string; 23 | }; 24 | pinia: { 25 | namespace: string; 26 | }; 27 | cookie: 28 | | { 29 | prefix: string; 30 | options: { 31 | path: string; 32 | expires?: number | Date; 33 | maxAge?: number; 34 | domain?: string; 35 | secure?: boolean; 36 | }; 37 | } 38 | | false; 39 | localStorage: { prefix: string; } | false; 40 | sessionStorage: { prefix: string; } | false; 41 | initialState?: { 42 | user: null; 43 | loggedIn: boolean; 44 | strategy?: string | null; 45 | busy?: boolean | null; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs-alt/proxy", 3 | "version": "2.1.0", 4 | "description": "An alternative module to @nuxtjs/proxy", 5 | "homepage": "https://github.com/Teranode/nuxt-module-alternatives", 6 | "author": "Teranode", 7 | "keywords": [ 8 | "nuxt", 9 | "nuxtjs", 10 | "nuxt-module", 11 | "nuxt-plugin", 12 | "nuxt-module-alternatives", 13 | "@nuxtjs/proxy" 14 | ], 15 | "license": "MIT", 16 | "type": "module", 17 | "main": "./dist/module.cjs", 18 | "module": "./dist/module.mjs", 19 | "types": "./dist/types.d.ts", 20 | "scripts": { 21 | "dev": "nuxi dev playground", 22 | "dev:build": "nuxi build playground", 23 | "dev:prepare": "unbuild --stub && nuxi prepare playground", 24 | "prepack": "unbuild" 25 | }, 26 | "files": [ 27 | "dist" 28 | ], 29 | "dependencies": { 30 | "@nuxt/kit": "^3.0.0", 31 | "@refactorjs/http-proxy": "latest", 32 | "defu": "latest", 33 | "ofetch": "latest" 34 | }, 35 | "devDependencies": { 36 | "unbuild": "latest", 37 | "nuxt": "^3.0.0" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/Teranode/nuxt-module-alternatives.git", 42 | "directory": "@nuxtjs-alt/proxy" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/Teranode/nuxt-module-alternatives/issues" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { ProxyServer, Server } from '@refactorjs/http-proxy' 2 | import type { IncomingMessage, ServerResponse } from 'node:http' 3 | import * as NuxtSchema from '@nuxt/schema'; 4 | 5 | export interface ModuleOptions { 6 | enableProxy?: boolean 7 | proxies?: { 8 | [key: string]: string | ProxyOptions 9 | } 10 | fetch?: boolean 11 | } 12 | 13 | export interface ProxyOptions extends Server.ServerOptions { 14 | /** 15 | * rewrite path 16 | */ 17 | rewrite?: (path: string) => string | null | undefined | false 18 | 19 | /** 20 | * configure the proxy server (e.g. listen to events) 21 | */ 22 | configure?: (proxy: ProxyServer, options: ProxyOptions) => void | null | undefined | false 23 | 24 | /** 25 | * webpack-dev-server style bypass function 26 | */ 27 | bypass?: ( 28 | req: IncomingMessage, 29 | res: ServerResponse, 30 | options: ProxyOptions 31 | ) => void | null | undefined | false | string 32 | } 33 | 34 | declare module '@nuxt/schema' { 35 | interface NuxtConfig { 36 | proxy?: ModuleOptions 37 | } 38 | interface NuxtOptions { 39 | proxy?: ModuleOptions 40 | } 41 | interface RuntimeConfig { 42 | proxy?: ModuleOptions; 43 | } 44 | interface PublicRuntimeConfig { 45 | proxy?: ModuleOptions 46 | } 47 | } 48 | 49 | declare const NuxtProxy: NuxtSchema.NuxtModule 50 | 51 | export default NuxtProxy -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/openIDConnectConfigurationDocument.d.ts: -------------------------------------------------------------------------------- 1 | export type OpenIDConnectConfigurationDocument = { 2 | /* eslint-disable camelcase */ 3 | issuer?: string; 4 | authorization_endpoint?: string; 5 | token_endpoint?: string; 6 | token_endpoint_auth_methods_supported?: string[]; 7 | token_endpoint_auth_signing_alg_values_supported?: string[]; 8 | userinfo_endpoint?: string; 9 | check_session_iframe?: string; 10 | end_session_endpoint?: string; 11 | jwks_uri?: string; 12 | registration_endpoint?: string; 13 | scopes_supported?: string[]; 14 | response_types_supported?: string[]; 15 | acr_values_supported?: string[]; 16 | response_modes_supported?: string[]; 17 | grant_types_supported?: string[]; 18 | subject_types_supported?: string[]; 19 | userinfo_signing_alg_values_supported?: string[]; 20 | userinfo_encryption_alg_values_supported?: string[]; 21 | userinfo_encryption_enc_values_supported?: string[]; 22 | id_token_signing_alg_values_supported?: string[]; 23 | id_token_encryption_alg_values_supported?: string[]; 24 | id_token_encryption_enc_values_supported?: string[]; 25 | request_object_signing_alg_values_supported?: string[]; 26 | display_values_supported?: string[]; 27 | claim_types_supported?: string[]; 28 | claims_supported?: string[]; 29 | claims_parameter_supported?: boolean; 30 | service_documentation?: string; 31 | ui_locales_supported?: string[]; 32 | /* eslint-enable camelcase */ 33 | }; 34 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs-alt/vuetify", 3 | "version": "1.0.4", 4 | "description": "A module to handle vuetify 3", 5 | "homepage": "https://github.com/Teranode/nuxt-module-alternatives", 6 | "author": "Teranode", 7 | "keywords": [ 8 | "nuxt", 9 | "nuxtjs", 10 | "nuxt-module", 11 | "nuxt-plugin", 12 | "nuxt-module-alternatives", 13 | "vuetify" 14 | ], 15 | "license": "MIT", 16 | "type": "module", 17 | "main": "./dist/module.cjs", 18 | "module": "./dist/module.mjs", 19 | "types": "./dist/types.d.ts", 20 | "files": [ 21 | "dist" 22 | ], 23 | "scripts": { 24 | "dev": "nuxi dev playground", 25 | "dev:build": "nuxi build playground", 26 | "prepack": "unbuild", 27 | "dev:prepare": "unbuild --stub && nuxi prepare playground" 28 | }, 29 | "dependencies": { 30 | "@nuxt/kit": "^3.0.0", 31 | "sass": "^1.56.1", 32 | "sass-loader": "^13.2.0", 33 | "vite-plugin-vuetify": "^1.0.0", 34 | "vuetify": "^3.0.2" 35 | }, 36 | "devDependencies": { 37 | "unbuild": "latest", 38 | "nuxt": "^3.0.0" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/Teranode/nuxt-module-alternatives.git", 43 | "directory": "@nuxtjs-alt/vuetify" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/Teranode/nuxt-module-alternatives/issues" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/readme.md: -------------------------------------------------------------------------------- 1 | ## This package is deprecated please use the package at: https://github.com/nuxt-alt/vuetify 2 | 3 | This module is meant to try to work with Vuetify 3. Here is the config for you to work with 4 | 5 | ```ts 6 | vuetify: { 7 | vuetifyOptions: { 8 | 9 | }, 10 | pluginOptions: { 11 | autoImports: true, // default 12 | styles: true // default 13 | }, 14 | } 15 | ``` 16 | 17 | **vuetifyOptions** 18 | 19 | This is for the configuration of vuetify located here: 20 | https://next.vuetifyjs.com/en/features/global-configuration/ 21 | 22 | Material Design Icons is used by default via cdn urls. you can use `mdi`, `md` or `fa` for cdn urls when setting `defaultSet` in vuetify options. 23 | 24 | **pluginOptions** 25 | 26 | Please refer to the Readme located here: 27 | https://www.npmjs.com/package/vite-plugin-vuetify 28 | 29 | By default the styles will be set to true and will load `vuetify/styles`. 30 | 31 | **Bugs** 32 | 33 | Please note that there is a bug with vuetify where custome scss files might not function correctly 34 | 35 | Source: https://github.com/nuxt/framework/issues/8825 36 | 37 | There's currently a bug with vueuse/head where it's potentially causing a memory leak and also not adapting changes when using the `useTheme` composable. For now I have made a workaround where the module will be running it's own `creatVuetify` instance inheriting from vueitfy and making the necissary changes to make it function. Until this is fixed thta will be used instead. 38 | 39 | Source: https://github.com/vuetifyjs/vuetify/issues/16156 40 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/laravel-sanctum.ts: -------------------------------------------------------------------------------- 1 | import type { ProviderPartialOptions, HTTPRequest, ProviderOptions } from '../types'; 2 | import type { CookieSchemeOptions } from '../runtime'; 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { assignDefaults } from '../utils/provider'; 5 | 6 | export interface LaravelSanctumProviderOptions extends ProviderOptions, CookieSchemeOptions {} 7 | 8 | export function laravelSanctum(nuxt: Nuxt, strategy: ProviderPartialOptions): void { 9 | const endpointDefaults: Partial = { 10 | credentials: 'include' 11 | }; 12 | 13 | const DEFAULTS: typeof strategy = { 14 | scheme: 'cookie', 15 | name: 'laravelSanctum', 16 | cookie: { 17 | name: 'XSRF-TOKEN', 18 | server: nuxt.options.ssr 19 | }, 20 | endpoints: { 21 | csrf: { 22 | ...endpointDefaults, 23 | url: '/sanctum/csrf-cookie', 24 | }, 25 | login: { 26 | ...endpointDefaults, 27 | url: '/login', 28 | }, 29 | logout: { 30 | ...endpointDefaults, 31 | url: '/logout', 32 | }, 33 | user: { 34 | ...endpointDefaults, 35 | url: '/api/user', 36 | }, 37 | }, 38 | user: { 39 | property: { 40 | server: false, 41 | client: false 42 | }, 43 | autoFetch: true, 44 | } 45 | }; 46 | 47 | assignDefaults(strategy, DEFAULTS) 48 | } 49 | -------------------------------------------------------------------------------- /@nuxtjs-alt/http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs-alt/http", 3 | "version": "1.5.0", 4 | "description": "An extended module to ohmyfetch", 5 | "homepage": "https://github.com/Teranode/nuxt-module-alternatives", 6 | "author": "Teranode", 7 | "keywords": [ 8 | "nuxt", 9 | "nuxtjs", 10 | "nuxt-module", 11 | "nuxt-plugin", 12 | "nuxt-module-alternatives", 13 | "ohmyfetch", 14 | "@nuxtjs/http" 15 | ], 16 | "license": "MIT", 17 | "type": "module", 18 | "sideEffects": false, 19 | "exports": { 20 | ".": { 21 | "import": "./dist/module.mjs", 22 | "require": "./dist/module.cjs" 23 | } 24 | }, 25 | "main": "./dist/module.cjs", 26 | "module": "./dist/module.mjs", 27 | "types": "./dist/types.d.ts", 28 | "files": [ 29 | "dist" 30 | ], 31 | "scripts": { 32 | "dev": "nuxi dev playground", 33 | "dev:build": "nuxi build playground", 34 | "dev:prepare": "unbuild --stub && nuxi prepare playground", 35 | "prepack": "unbuild" 36 | }, 37 | "dependencies": { 38 | "@nuxt/kit": "^3.0.0", 39 | "@refactorjs/ofetch": "latest", 40 | "defu": "latest", 41 | "ufo": "latest" 42 | }, 43 | "devDependencies": { 44 | "nuxt": "^3.0.0", 45 | "unbuild": "latest" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "git+https://github.com/Teranode/nuxt-module-alternatives.git", 50 | "directory": "@nuxtjs-alt/http" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/Teranode/nuxt-module-alternatives/issues" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/laravel-jwt.ts: -------------------------------------------------------------------------------- 1 | import type { ProviderPartialOptions, ProviderOptions } from '../types'; 2 | import type { RefreshSchemeOptions } from '../runtime'; 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { assignDefaults, assignAbsoluteEndpoints } from '../utils/provider'; 5 | 6 | export interface LaravelJWTProviderOptions extends ProviderOptions, RefreshSchemeOptions { 7 | url: string; 8 | } 9 | 10 | export function laravelJWT(nuxt: Nuxt, strategy: ProviderPartialOptions): void { 11 | const { url } = strategy; 12 | 13 | if (!url) { 14 | throw new Error('url is required for laravel jwt!'); 15 | } 16 | 17 | const DEFAULTS: typeof strategy = { 18 | name: 'laravelJWT', 19 | scheme: 'laravelJWT', 20 | endpoints: { 21 | login: { 22 | url: url + '/api/auth/login', 23 | }, 24 | refresh: { 25 | url: url + '/api/auth/refresh', 26 | }, 27 | logout: { 28 | url: url + '/api/auth/logout', 29 | }, 30 | user: { 31 | url: url + '/api/auth/user', 32 | }, 33 | }, 34 | token: { 35 | property: 'access_token', 36 | maxAge: 3600, 37 | }, 38 | refreshToken: { 39 | property: false, 40 | data: false, 41 | maxAge: 1209600, 42 | required: false, 43 | tokenRequired: true, 44 | }, 45 | user: { 46 | property: false, 47 | }, 48 | clientId: false, 49 | grantType: false, 50 | }; 51 | 52 | assignDefaults(strategy, DEFAULTS); 53 | 54 | assignAbsoluteEndpoints(strategy); 55 | } 56 | -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs-alt/axios", 3 | "version": "1.0.19", 4 | "description": "An alternative module to @nuxtjs/axios", 5 | "homepage": "https://github.com/Teranode/nuxt-module-alternatives", 6 | "author": "Teranode", 7 | "keywords": [ 8 | "nuxt", 9 | "nuxtjs", 10 | "nuxt-module", 11 | "nuxt-plugin", 12 | "nuxt-module-alternatives", 13 | "axios", 14 | "@nuxtjs/axios" 15 | ], 16 | "license": "MIT", 17 | "type": "module", 18 | "sideEffects": false, 19 | "exports": { 20 | ".": { 21 | "import": "./dist/module.mjs", 22 | "require": "./dist/module.cjs" 23 | } 24 | }, 25 | "main": "./dist/module.cjs", 26 | "module": "./dist/module.mjs", 27 | "types": "./dist/module.d.ts", 28 | "files": [ 29 | "dist" 30 | ], 31 | "scripts": { 32 | "dev": "nuxi dev playground", 33 | "dev:build": "nuxi build playground", 34 | "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground", 35 | "prepack": "unbuild" 36 | }, 37 | "dependencies": { 38 | "@nuxt/kit": "^3.0.0-rc.9", 39 | "axios": "^0.27.2", 40 | "axios-retry": "^3.3.1", 41 | "defu": "^6.1.0" 42 | }, 43 | "devDependencies": { 44 | "@nuxt/module-builder": "^0.1.7", 45 | "@nuxt/schema": "3.0.0-rc.9", 46 | "@types/node": "^18.7.15", 47 | "nuxt": "^3.0.0-rc.9", 48 | "unbuild": "^0.8.10" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "git+https://github.com/Teranode/nuxt-module-alternatives.git", 53 | "directory": "@nuxtjs-alt/axios" 54 | }, 55 | "bugs": { 56 | "url": "https://github.com/Teranode/nuxt-module-alternatives/issues" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs-alt/auth", 3 | "version": "2.1.5", 4 | "description": "An alternative module to @nuxtjs/auth", 5 | "homepage": "https://github.com/Teranode/nuxt-module-alternatives", 6 | "author": "Teranode", 7 | "keywords": [ 8 | "nuxt", 9 | "nuxtjs", 10 | "nuxt-module", 11 | "nuxt-plugin", 12 | "nuxt-module-alternatives", 13 | "@nuxtjs/auth" 14 | ], 15 | "license": "MIT", 16 | "type": "module", 17 | "sideEffects": false, 18 | "exports": { 19 | ".": { 20 | "import": "./dist/module.mjs", 21 | "require": "./dist/module.cjs" 22 | } 23 | }, 24 | "main": "./dist/module.cjs", 25 | "module": "./dist/module.mjs", 26 | "types": "./dist/types/index.d.ts", 27 | "files": [ 28 | "dist" 29 | ], 30 | "scripts": { 31 | "dev": "nuxi dev playground", 32 | "dev:build": "nuxi build playground", 33 | "dev:prepare": "unbuild --stub && nuxi prepare playground", 34 | "prepack": "unbuild" 35 | }, 36 | "dependencies": { 37 | "@nuxt/kit": "^3.0.0", 38 | "@nuxtjs-alt/http": "latest", 39 | "@pinia/nuxt": "latest", 40 | "body-parser": "latest", 41 | "cookie-es": "latest", 42 | "jwt-decode": "latest", 43 | "requrl": "latest", 44 | "defu": "latest", 45 | "ohash": "latest", 46 | "pathe": "latest" 47 | }, 48 | "devDependencies": { 49 | "@nuxt/schema": "3.0.0", 50 | "fdir": "latest", 51 | "nuxt": "^3.0.0", 52 | "unbuild": "latest" 53 | }, 54 | "repository": { 55 | "type": "git", 56 | "url": "git+https://github.com/Teranode/nuxt-module-alternatives.git", 57 | "directory": "@nuxtjs-alt/auth" 58 | }, 59 | "bugs": { 60 | "url": "https://github.com/Teranode/nuxt-module-alternatives/issues" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { ImportOptions } from './resolve' 2 | import { ModuleOptions, Strategy } from './types' 3 | 4 | export const getAuthDTS = () => { 5 | return `import type { Plugin } from '#app' 6 | import { Auth } from '#auth/runtime' 7 | 8 | declare const _default: Plugin<{ 9 | auth: Auth; 10 | }>; 11 | 12 | export default _default; 13 | ` 14 | } 15 | 16 | export const getAuthPlugin = (options: { 17 | options: ModuleOptions 18 | schemeImports: ImportOptions[] 19 | strategies: Strategy[] 20 | strategyScheme: Record 21 | }): string => { 22 | return `import { Auth, ExpiredAuthSessionError } from '#auth/runtime' 23 | import { defineNuxtPlugin } from '#imports' 24 | // Active schemes 25 | ${options.schemeImports.map((i) => `import { ${i.name}${i.name !== i.as ? ' as ' + i.as : ''} } from '${i.from}'`).join('\n')} 26 | 27 | export default defineNuxtPlugin(nuxtApp => { 28 | // Options 29 | const options = ${JSON.stringify(options.options, null, 2)} 30 | 31 | // Create a new Auth instance 32 | const auth = new Auth(nuxtApp, options) 33 | 34 | // Register strategies 35 | ${options.strategies.map((strategy) => { 36 | const scheme = options.strategyScheme[strategy.name!] 37 | const schemeOptions = JSON.stringify(strategy, null, 2) 38 | return `auth.registerStrategy('${strategy.name}', new ${scheme.as}(auth, ${schemeOptions}));` 39 | }).join(';\n')} 40 | 41 | nuxtApp.provide('auth', auth) 42 | 43 | return auth.init().catch(error => { 44 | if (process.client) { 45 | // Don't console log expired auth session errors. This error is common, and expected to happen. 46 | // The error happens whenever the user does an ssr request (reload/initial navigation) with an expired refresh 47 | // token. We don't want to log this as an error. 48 | if (error instanceof ExpiredAuthSessionError) { 49 | return 50 | } 51 | 52 | console.error('[ERROR] [AUTH]', error) 53 | } 54 | }) 55 | })` 56 | } 57 | -------------------------------------------------------------------------------- /@nuxtjs-alt/http/src/runtime/templates/http.plugin.mjs: -------------------------------------------------------------------------------- 1 | import { createInstance } from '@refactorjs/ofetch' 2 | import { defineNuxtPlugin } from '#imports' 3 | 4 | // Nuxt Options 5 | const options = JSON.parse('<%= JSON.stringify(options) %>') 6 | 7 | const httpInstance = (options) => { 8 | // Create new Fetch instance 9 | const instance = createInstance(options) 10 | '<% if (options.debug) { %>';debugInterceptor(instance);'<% } %>' 11 | 12 | return instance 13 | } 14 | 15 | '<% if (options.debug) { %>' 16 | const debugInterceptor = http => { 17 | const log = (level, ...messages) => console[level]('[http]', ...messages) 18 | 19 | // request 20 | http.onRequest(config => { 21 | log('info', 'Request:', config) 22 | return config 23 | }) 24 | 25 | http.onRequestError(error => { 26 | log('error', 'Request error:', error) 27 | }) 28 | 29 | // response 30 | http.onResponse(res => { 31 | log('info', 'Response:', res) 32 | return res 33 | }) 34 | 35 | http.onResponseError(error => { 36 | log('error', 'Response error:', error) 37 | }) 38 | } 39 | '<% } %>' 40 | 41 | export default defineNuxtPlugin(ctx => { 42 | // baseURL 43 | const baseURL = process.client ? options.browserBaseURL : options.baseURL 44 | 45 | // Defaults 46 | const defaults = { 47 | baseURL, 48 | retry: options.retry, 49 | timeout: process.server ? options.serverTimeout : options.clientTimeout, 50 | credentials: options.credentials, 51 | headers: options.headers, 52 | } 53 | 54 | if (options.proxyHeaders) { 55 | // Proxy SSR request headers 56 | if (process.server && ctx.ssrContext?.event?.req?.headers) { 57 | const reqHeaders = { ...ctx.ssrContext.event.req.headers } 58 | for (const h of options.proxyHeadersIgnore) { 59 | delete reqHeaders[h] 60 | } 61 | 62 | defaults.headers = { ...reqHeaders, ...defaults.headers } 63 | } 64 | } 65 | 66 | const http = httpInstance(defaults) 67 | 68 | if (!globalThis.$http) { 69 | globalThis.$http = http 70 | } 71 | 72 | return { 73 | provide: { 74 | http: http 75 | } 76 | } 77 | }) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug report" 2 | description: Create a report to help us improve Nuxt 3 | labels: ["pending triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Please use a template below to create a minimal reproduction 9 | 👉 https://stackblitz.com/github/nuxt/starter/tree/v3-stackblitz 10 | 👉 https://codesandbox.io/p/github/nuxt/starter/v3-codesandbox 11 | - type: textarea 12 | id: bug-env 13 | attributes: 14 | label: Environment 15 | description: You can use `npx nuxi info` to fill this section 16 | placeholder: Environment 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: nuxt-config 21 | attributes: 22 | label: Nuxt Config 23 | description: Please provide your nuxt config in here, if you do not provide it, this issue will be ignored. 24 | placeholder: Nuxt Config 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: reproduction 29 | attributes: 30 | label: Reproduction 31 | description: Please provide a link to a repo that can reproduce the problem you ran into. A [**minimal reproduction**](https://v3.nuxtjs.org/community/reporting-bugs#create-a-minimal-reproduction) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided we might close it. 32 | placeholder: Reproduction 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: bug-description 37 | attributes: 38 | label: Describe the bug 39 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 40 | placeholder: Bug description 41 | validations: 42 | required: true 43 | - type: textarea 44 | id: additonal 45 | attributes: 46 | label: Additional context 47 | description: If applicable, add any other context about the problem here 48 | - type: textarea 49 | id: logs 50 | attributes: 51 | label: Logs 52 | description: | 53 | Optional if provided reproduction. Please try not to insert an image but copy paste the log text. 54 | render: shell 55 | -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/readme.md: -------------------------------------------------------------------------------- 1 | **Information** 2 | 3 | This serves as an alternative for @nuxtjs-alt/proxy. Please note that his is for nuxt3 only. 4 | 5 | **Other Information** 6 | 7 | This module creates a file in your `buildDir` called `nuxt-http-proxy.ts` which will handle all of the proxying you set within your nuxt config. The config is similar to what vite has except that this one creates a physical file which is needed for production. 8 | 9 | **Version 2.0+** 10 | New options have been added to the proxy module. The proxies now need to be moved into a `proxies` property (example provided below). A `fetch` property has been added so that proxying applies to the native `$fetch` in nitro and via client side. An `enableProxy` property has been added if you would like to disable the `http-proxy` creation for some reason. 11 | 12 | **Configuration** 13 | 14 | The configuration looks similar to that of vite's server proxy config, only difference is that it's passed through to nuxt server handler. 15 | 16 | ```ts 17 | import { defineNuxtConfig } from 'nuxt/config' 18 | 19 | export default defineNuxtConfig({ 20 | modules: [ 21 | '@nuxtjs-alt/proxy', 22 | ], 23 | proxy: { 24 | enableProxy: true, 25 | proxies: { 26 | // string shorthand 27 | '/foo': 'http://localhost:4567', 28 | // with options 29 | '/api': { 30 | target: 'http://jsonplaceholder.typicode.com', 31 | changeOrigin: true, 32 | rewrite: (path) => path.replace(/^\/api/, '') 33 | }, 34 | // with RegEx 35 | '^/fallback/.*': { 36 | target: 'http://jsonplaceholder.typicode.com', 37 | changeOrigin: true, 38 | rewrite: (path) => path.replace(/^\/fallback/, '') 39 | }, 40 | // Using the proxy instance 41 | '/api': { 42 | target: 'http://jsonplaceholder.typicode.com', 43 | changeOrigin: true, 44 | configure: (proxy, options) => { 45 | // proxy will be an instance of 'http-proxy' 46 | } 47 | }, 48 | // Proxying websockets or socket.io 49 | '/socket.io': { 50 | target: 'ws://localhost:5173', 51 | ws: true 52 | } 53 | }, 54 | fetch: true 55 | } 56 | }) 57 | 58 | ``` -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/core/middleware.ts: -------------------------------------------------------------------------------- 1 | import { routeMeta, getMatchedComponents, normalizePath } from '../../utils'; 2 | import { useNuxtApp, defineNuxtRouteMiddleware } from '#imports'; 3 | 4 | export default defineNuxtRouteMiddleware(async (to, from) => { 5 | // Disable middleware if options: { auth: false } is set on the route 6 | if (Object.prototype.hasOwnProperty.call(to.meta, 'auth') && routeMeta('auth', false)) { 7 | return; 8 | } 9 | 10 | // Disable middleware if no route was matched to allow 404/error page 11 | const matches: unknown[] = []; 12 | const Components = getMatchedComponents(matches); 13 | 14 | if (!Components.length) { 15 | return; 16 | } 17 | 18 | const ctx = useNuxtApp(); 19 | 20 | const { login, callback } = ctx.$auth.options.redirect; 21 | 22 | const pageIsInGuestMode = Object.prototype.hasOwnProperty.call(to.meta, 'auth') && routeMeta('auth', 'guest'); 23 | 24 | const insidePage = (page: string) => normalizePath(to.path) === normalizePath(page); 25 | 26 | if (ctx.$auth.$state.loggedIn) { 27 | // Perform scheme checks. 28 | const { tokenExpired, refreshTokenExpired, isRefreshable } = ctx.$auth.check(true); 29 | 30 | // -- Authorized -- 31 | if (!login || insidePage(login) || pageIsInGuestMode) { 32 | ctx.$auth.redirect('home', to); 33 | } 34 | 35 | // Refresh token has expired. There is no way to refresh. Force reset. 36 | if (refreshTokenExpired) { 37 | ctx.$auth.reset(); 38 | } else if (tokenExpired) { 39 | // Token has expired. Check if refresh token is available. 40 | if (isRefreshable) { 41 | // Refresh token is available. Attempt refresh. 42 | try { 43 | await ctx.$auth.refreshTokens(); 44 | } catch (error) { 45 | // Reset when refresh was not successfull 46 | ctx.$auth.reset(); 47 | } 48 | } else { 49 | // Refresh token is not available. Force reset. 50 | ctx.$auth.reset(); 51 | } 52 | } 53 | } 54 | 55 | // -- Guest -- 56 | // (Those passing `callback` at runtime need to mark their callback component 57 | // with `auth: false` to avoid an unnecessary redirect from callback to login) 58 | else if (!pageIsInGuestMode && (!callback || !insidePage(callback))) { 59 | ctx.$auth.redirect('login', to); 60 | } 61 | }); 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nuxt-module-alternatives 2 | Alternative modules to use while waiting for Nuxt 3 Compatibility 3 | 4 | ## Migration 5 | 6 | I will be migrating most of these modules over to https://github.com/orgs/nuxt-alt/repositories. This will take me a bit to set up. The module's versions will be incremented by 1 to account for the migration. Each module will have their own separate repository. 7 | 8 | | Old Package Name | New Package Name | Link 9 | | --- | --- | --- | 10 | | @nuxtjs-alt/vuetify | @nuxt-alt/vuetify | https://github.com/nuxt-alt/vuetify 11 | | @nuxtjs-alt/auth | @nuxt-alt/auth | https://github.com/nuxt-alt/auth 12 | | @nuxtjs-alt/http | @nuxt-alt/http | https://github.com/nuxt-alt/http 13 | | @nuxtjs-alt/proxy | @nuxt-alt/proxy | https://github.com/nuxt-alt/proxy 14 | 15 | **Current Modules** 16 | - Nuxt Axios Module: [Nuxt Community Repository](https://github.com/nuxt-community/axios-module) 17 | - Nuxt Http Module: [Nuxt Community Module](https://github.com/nuxt/http) 18 | - Nuxt Proxy Module: [Nuxt Community Repository](https://github.com/nuxt-community/proxy-module) 19 | - Nuxt Auth Module: [Nuxt Community Repository](https://github.com/nuxt-community/auth-module) 20 | - Nuxt Vuetify Module: [Nuxt Community Repository](https://github.com/nuxt-community/vuetify-module) 21 | 22 | **Module Order** 23 | 24 | If you're using a combination of http/ohmyfetch, pinia and auth you need to load them in `modules` in the following order. 25 | ``` 26 | modules: [ 27 | '@nuxtjs-alt/auth', 28 | '@nuxtjs-alt/http', 29 | '@nuxtjs-alt/proxy', // needed if using ssr 30 | '@pinia/nuxt', 31 | ] 32 | ``` 33 | 34 | **Instructions** 35 | 36 | - Add any of the modules available via npm (package list: https://www.npmjs.com/org/nuxtjs-alt) 37 | 38 | **Other Modules** 39 | 40 | _If you have a nuxt module that looks like it wont be updated, and has any usefeulness to the general nuxt community, please tell me and I'll take a look into it._ 41 | 42 | Example `package.json`: 43 |
44 | package.json 45 | 46 | `yarn install` 47 | 48 | ```json 49 | { 50 | "private": true, 51 | "scripts": { 52 | "dev": "nuxi dev", 53 | "build": "nuxi build", 54 | "start": "node .output/server/index.mjs" 55 | }, 56 | "devDependencies": { 57 | "nuxt": "npm:nuxt3@latest" 58 | }, 59 | "dependencies": { 60 | "@nuxtjs-alt/axios": "latest", 61 | "@nuxtjs-alt/auth": "latest", 62 | "@nuxtjs-alt/http": "latest", 63 | "@nuxtjs-alt/proxy": "latest", 64 | "@nuxtjs-alt/vuetify": "latest" 65 | } 66 | } 67 | ``` 68 |
69 | -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/build.config.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtModule } from '@nuxt/schema' 2 | import { defineBuildConfig } from "unbuild"; 3 | import { existsSync, promises as fsp } from 'fs' 4 | import { pathToFileURL } from 'url' 5 | import { resolve } from 'path' 6 | 7 | export default defineBuildConfig({ 8 | declaration: true, 9 | entries: [ 10 | 'src/module', 11 | { input: 'src/runtime/', outDir: 'dist/runtime', ext: 'mjs' }, 12 | ], 13 | rollup: { 14 | emitCJS: false, 15 | cjsBridge: true, 16 | }, 17 | externals: [ 18 | '@nuxt/schema', 19 | '@nuxt/schema-edge', 20 | '@nuxt/kit', 21 | '@nuxt/kit-edge', 22 | 'nuxt', 23 | 'nuxt-edge', 24 | 'nuxt3', 25 | 'vue' 26 | ], 27 | hooks: { 28 | async 'rollup:done' (ctx) { 29 | // Generate CommonJS stup 30 | await writeCJSStub(ctx.options.outDir) 31 | 32 | // Load module meta 33 | const moduleEntryPath = resolve(ctx.options.outDir, 'module.mjs') 34 | const moduleFn: NuxtModule = await import( 35 | pathToFileURL(moduleEntryPath).toString() 36 | ).then(r => r.default || r).catch((err) => { 37 | console.error(err) 38 | console.error('Cannot load module. Please check dist:', moduleEntryPath) 39 | return null 40 | }) 41 | if (!moduleFn) { 42 | return 43 | } 44 | const moduleMeta = await moduleFn.getMeta!() 45 | 46 | // Enhance meta using package.json 47 | if (ctx.pkg) { 48 | if (!moduleMeta.name) { 49 | moduleMeta.name = ctx.pkg.name 50 | } 51 | if (!moduleMeta.version) { 52 | moduleMeta.version = ctx.pkg.version 53 | } 54 | } 55 | 56 | // Write meta 57 | const metaFile = resolve(ctx.options.outDir, 'module.json') 58 | await fsp.writeFile(metaFile, JSON.stringify(moduleMeta, null, 2), 'utf8') 59 | } 60 | } 61 | }); 62 | 63 | async function writeCJSStub (distDir: string) { 64 | const cjsStubFile = resolve(distDir, 'module.cjs') 65 | if (existsSync(cjsStubFile)) { 66 | return 67 | } 68 | 69 | const cjsStub = 70 | `module.exports = function(...args) { 71 | return import('./module.mjs').then(m => m.default.call(this, ...args)) 72 | } 73 | const _meta = module.exports.meta = require('./module.json') 74 | module.exports.getMeta = () => Promise.resolve(_meta)` 75 | 76 | await fsp.writeFile(cjsStubFile, cjsStub, 'utf8') 77 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosStatic, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'; 2 | import type { IAxiosRetryConfig } from 'axios-retry'; 3 | 4 | export interface NuxtAxiosInstance extends AxiosStatic { 5 | $request(config: AxiosRequestConfig): Promise; 6 | $get(url: string, config?: AxiosRequestConfig): Promise; 7 | $delete(url: string, config?: AxiosRequestConfig): Promise; 8 | $head(url: string, config?: AxiosRequestConfig): Promise; 9 | $options(url: string, config?: AxiosRequestConfig): Promise; 10 | $post(url: string, data?: any, config?: AxiosRequestConfig): Promise; 11 | $put(url: string, data?: any, config?: AxiosRequestConfig): Promise; 12 | $patch(url: string, data?: any, config?: AxiosRequestConfig): Promise; 13 | setBaseURL(baseURL: string): void; 14 | setHeader(name: string, value?: string | false, scopes?: string | string[]): void; 15 | setToken(token: string | false, type?: string, scopes?: string | string[]): void; 16 | onRequest(callback: (config: AxiosRequestConfig) => void | AxiosRequestConfig | Promise): void; 17 | onResponse(callback: (response: AxiosResponse) => void | AxiosResponse | Promise>): void; 18 | onError(callback: (error: AxiosError) => any): void; 19 | onRequestError(callback: (error: AxiosError) => any): void; 20 | onResponseError(callback: (error: AxiosError) => any): void; 21 | create(options?: AxiosRequestConfig): NuxtAxiosInstance; 22 | } 23 | 24 | export interface ModuleOptions { 25 | baseURL: string; 26 | baseUrl?: string; 27 | browserBaseURL: string; 28 | browserBaseUrl?: string; 29 | globalName?: string; 30 | credentials?: boolean; 31 | debug?: boolean; 32 | host?: string; 33 | prefix?: string; 34 | progress?: boolean; 35 | proxyHeaders?: boolean; 36 | proxyHeadersIgnore?: string[]; 37 | proxy?: boolean; 38 | port?: string | number; 39 | retry?: boolean | IAxiosRetryConfig; 40 | https?: boolean; 41 | headers?: { 42 | common?: Record; 43 | delete?: Record; 44 | get?: Record; 45 | head?: Record; 46 | post?: Record; 47 | put?: Record; 48 | patch?: Record; 49 | }; 50 | } 51 | 52 | declare module "axios" { 53 | interface AxiosRequestConfig { 54 | progress?: boolean; 55 | } 56 | } 57 | 58 | declare module '@nuxt/schema' { 59 | export interface NuxtConfig { 60 | ['axios']?: Partial; 61 | } 62 | export interface NuxtOptions { 63 | ['axios']?: ModuleOptions; 64 | } 65 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/http/build.config.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtModule } from '@nuxt/schema' 2 | import { existsSync, promises as fsp } from 'node:fs' 3 | import { defineBuildConfig } from "unbuild" 4 | import { pathToFileURL } from 'node:url' 5 | import { resolve } from 'pathe' 6 | import mri from 'mri' 7 | 8 | const args = mri(process.argv.slice(2)) 9 | 10 | export default defineBuildConfig({ 11 | failOnWarn: false, 12 | declaration: true, 13 | stub: args.stub, 14 | entries: [ 15 | 'src/module', 16 | 'src/types', 17 | { input: 'src/runtime/', outDir: 'dist/runtime', ext: 'mjs' }, 18 | ], 19 | rollup: { 20 | emitCJS: false, 21 | cjsBridge: true, 22 | }, 23 | externals: [ 24 | '@nuxt/schema', 25 | '@nuxt/schema-edge', 26 | '@nuxt/kit', 27 | '@nuxt/kit-edge', 28 | 'nuxt', 29 | 'nuxt-edge', 30 | 'nuxt3', 31 | 'vue', 32 | 'vue-demi' 33 | ], 34 | hooks: { 35 | async 'rollup:dts:build'(ctx) { 36 | // Types file 37 | const typesFile = resolve(ctx.options.outDir, 'types.mjs') 38 | await fsp.unlink(typesFile) 39 | }, 40 | async 'rollup:done' (ctx) { 41 | // Generate CommonJS stup 42 | await writeCJSStub(ctx.options.outDir) 43 | 44 | // Load module meta 45 | const moduleEntryPath = resolve(ctx.options.outDir, 'module.mjs') 46 | const moduleFn: NuxtModule = await import( 47 | pathToFileURL(moduleEntryPath).toString() 48 | ).then(r => r.default || r).catch((err) => { 49 | console.error(err) 50 | console.error('Cannot load module. Please check dist:', moduleEntryPath) 51 | return null 52 | }) 53 | if (!moduleFn) { 54 | return 55 | } 56 | const moduleMeta = await moduleFn.getMeta!() 57 | 58 | // Enhance meta using package.json 59 | if (ctx.pkg) { 60 | if (!moduleMeta.name) { 61 | moduleMeta.name = ctx.pkg.name 62 | } 63 | if (!moduleMeta.version) { 64 | moduleMeta.version = ctx.pkg.version 65 | } 66 | } 67 | 68 | // Write meta 69 | const metaFile = resolve(ctx.options.outDir, 'module.json') 70 | await fsp.writeFile(metaFile, JSON.stringify(moduleMeta, null, 2), 'utf8') 71 | } 72 | } 73 | }); 74 | 75 | async function writeCJSStub (distDir: string) { 76 | const cjsStubFile = resolve(distDir, 'module.cjs') 77 | if (existsSync(cjsStubFile)) { 78 | return 79 | } 80 | 81 | const cjsStub = 82 | `module.exports = function(...args) { 83 | return import('./module.mjs').then(m => m.default.call(this, ...args)) 84 | } 85 | const _meta = module.exports.meta = require('./module.json') 86 | module.exports.getMeta = () => Promise.resolve(_meta)` 87 | 88 | await fsp.writeFile(cjsStubFile, cjsStub, 'utf8') 89 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/build.config.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtModule } from '@nuxt/schema' 2 | import { existsSync, promises as fsp } from 'node:fs' 3 | import { defineBuildConfig } from "unbuild" 4 | import { pathToFileURL } from 'node:url' 5 | import { resolve } from 'pathe' 6 | import mri from 'mri' 7 | 8 | const args = mri(process.argv.slice(2)) 9 | 10 | export default defineBuildConfig({ 11 | failOnWarn: false, 12 | declaration: true, 13 | stub: args.stub, 14 | entries: [ 15 | 'src/module', 16 | 'src/types', 17 | { input: 'src/runtime/', outDir: 'dist/runtime', ext: 'mjs' }, 18 | ], 19 | rollup: { 20 | emitCJS: false, 21 | cjsBridge: true, 22 | }, 23 | externals: [ 24 | '@nuxt/schema', 25 | '@nuxt/schema-edge', 26 | '@nuxt/kit', 27 | '@nuxt/kit-edge', 28 | 'nuxt', 29 | 'nuxt-edge', 30 | 'nuxt3', 31 | 'vue', 32 | 'vue-demi' 33 | ], 34 | hooks: { 35 | async 'rollup:dts:build'(ctx) { 36 | // Types file 37 | const typesFile = resolve(ctx.options.outDir, 'types.mjs') 38 | await fsp.unlink(typesFile) 39 | }, 40 | async 'rollup:done' (ctx) { 41 | // Generate CommonJS stup 42 | await writeCJSStub(ctx.options.outDir) 43 | 44 | // Load module meta 45 | const moduleEntryPath = resolve(ctx.options.outDir, 'module.mjs') 46 | const moduleFn: NuxtModule = await import( 47 | pathToFileURL(moduleEntryPath).toString() 48 | ).then(r => r.default || r).catch((err) => { 49 | console.error(err) 50 | console.error('Cannot load module. Please check dist:', moduleEntryPath) 51 | return null 52 | }) 53 | if (!moduleFn) { 54 | return 55 | } 56 | const moduleMeta = await moduleFn.getMeta!() 57 | 58 | // Enhance meta using package.json 59 | if (ctx.pkg) { 60 | if (!moduleMeta.name) { 61 | moduleMeta.name = ctx.pkg.name 62 | } 63 | if (!moduleMeta.version) { 64 | moduleMeta.version = ctx.pkg.version 65 | } 66 | } 67 | 68 | // Write meta 69 | const metaFile = resolve(ctx.options.outDir, 'module.json') 70 | await fsp.writeFile(metaFile, JSON.stringify(moduleMeta, null, 2), 'utf8') 71 | } 72 | } 73 | }); 74 | 75 | async function writeCJSStub (distDir: string) { 76 | const cjsStubFile = resolve(distDir, 'module.cjs') 77 | if (existsSync(cjsStubFile)) { 78 | return 79 | } 80 | 81 | const cjsStub = 82 | `module.exports = function(...args) { 83 | return import('./module.mjs').then(m => m.default.call(this, ...args)) 84 | } 85 | const _meta = module.exports.meta = require('./module.json') 86 | module.exports.getMeta = () => Promise.resolve(_meta)` 87 | 88 | await fsp.writeFile(cjsStubFile, cjsStub, 'utf8') 89 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/build.config.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtModule } from '@nuxt/schema' 2 | import { existsSync, promises as fsp } from 'node:fs' 3 | import { defineBuildConfig } from "unbuild" 4 | import { pathToFileURL } from 'node:url' 5 | import { resolve } from 'pathe' 6 | import mri from 'mri' 7 | 8 | const args = mri(process.argv.slice(2)) 9 | 10 | export default defineBuildConfig({ 11 | declaration: true, 12 | stub: args.stub, 13 | externals: [ 14 | '@nuxt/schema', 15 | '@nuxt/schema-edge', 16 | '@nuxt/kit', 17 | '@nuxt/kit-edge', 18 | 'nuxt', 19 | 'nuxt-edge', 20 | 'nuxt3', 21 | 'vue', 22 | 'vue-demi', 23 | 'vuetify', 24 | 'defu' 25 | ], 26 | entries: [ 27 | 'src/module', 28 | 'src/types', 29 | { input: 'src/runtime/', outDir: 'dist/runtime', ext: 'mjs' }, 30 | ], 31 | rollup: { 32 | emitCJS: false, 33 | cjsBridge: true, 34 | }, 35 | hooks: { 36 | async 'rollup:dts:build'(ctx) { 37 | // Types file 38 | const typesFile = resolve(ctx.options.outDir, 'types.mjs') 39 | await fsp.unlink(typesFile) 40 | }, 41 | async 'rollup:done' (ctx) { 42 | // Generate CommonJS stup 43 | await writeCJSStub(ctx.options.outDir) 44 | 45 | // Load module meta 46 | const moduleEntryPath = resolve(ctx.options.outDir, 'module.mjs') 47 | const moduleFn: NuxtModule = await import( 48 | pathToFileURL(moduleEntryPath).toString() 49 | ).then(r => r.default || r).catch((err) => { 50 | console.error(err) 51 | console.error('Cannot load module. Please check dist:', moduleEntryPath) 52 | return null 53 | }) 54 | if (!moduleFn) { 55 | return 56 | } 57 | const moduleMeta = await moduleFn.getMeta!() 58 | 59 | // Enhance meta using package.json 60 | if (ctx.pkg) { 61 | if (!moduleMeta.name) { 62 | moduleMeta.name = ctx.pkg.name 63 | } 64 | if (!moduleMeta.version) { 65 | moduleMeta.version = ctx.pkg.version 66 | } 67 | } 68 | 69 | // Write meta 70 | const metaFile = resolve(ctx.options.outDir, 'module.json') 71 | await fsp.writeFile(metaFile, JSON.stringify(moduleMeta, null, 2), 'utf8') 72 | } 73 | } 74 | }); 75 | 76 | async function writeCJSStub (distDir: string) { 77 | const cjsStubFile = resolve(distDir, 'module.cjs') 78 | if (existsSync(cjsStubFile)) { 79 | return 80 | } 81 | 82 | const cjsStub = 83 | `module.exports = function(...args) { 84 | return import('./module.mjs').then(m => m.default.call(this, ...args)) 85 | } 86 | const _meta = module.exports.meta = require('./module.json') 87 | module.exports.getMeta = () => Promise.resolve(_meta)` 88 | 89 | await fsp.writeFile(cjsStubFile, cjsStub, 'utf8') 90 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/src/module.d.ts: -------------------------------------------------------------------------------- 1 | import * as NuxtSchema from '@nuxt/schema'; 2 | import { AxiosStatic, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'; 3 | import { IAxiosRetryConfig } from 'axios-retry'; 4 | 5 | interface NuxtAxiosInstance extends AxiosStatic { 6 | $request(config: AxiosRequestConfig): Promise; 7 | $get(url: string, config?: AxiosRequestConfig): Promise; 8 | $delete(url: string, config?: AxiosRequestConfig): Promise; 9 | $head(url: string, config?: AxiosRequestConfig): Promise; 10 | $options(url: string, config?: AxiosRequestConfig): Promise; 11 | $post(url: string, data?: any, config?: AxiosRequestConfig): Promise; 12 | $put(url: string, data?: any, config?: AxiosRequestConfig): Promise; 13 | $patch(url: string, data?: any, config?: AxiosRequestConfig): Promise; 14 | setBaseURL(baseURL: string): void; 15 | setHeader(name: string, value?: string | false, scopes?: string | string[]): void; 16 | setToken(token: string | false, type?: string, scopes?: string | string[]): void; 17 | onRequest(callback: (config: AxiosRequestConfig) => void | AxiosRequestConfig | Promise): void; 18 | onResponse(callback: (response: AxiosResponse) => void | AxiosResponse | Promise>): void; 19 | onError(callback: (error: AxiosError) => any): void; 20 | onRequestError(callback: (error: AxiosError) => any): void; 21 | onResponseError(callback: (error: AxiosError) => any): void; 22 | create(options?: AxiosRequestConfig): NuxtAxiosInstance; 23 | } 24 | 25 | interface ModuleOptions { 26 | baseURL?: string; 27 | baseUrl?: string; 28 | browserBaseURL?: string; 29 | browserBaseUrl?: string; 30 | globalName?: string; 31 | credentials?: boolean; 32 | debug?: boolean; 33 | host?: string; 34 | prefix?: string; 35 | progress?: boolean; 36 | proxyHeaders?: boolean; 37 | proxyHeadersIgnore?: string[]; 38 | proxy?: boolean; 39 | port?: string | number; 40 | retry?: boolean | IAxiosRetryConfig; 41 | https?: boolean; 42 | headers?: { 43 | common?: Record; 44 | delete?: Record; 45 | get?: Record; 46 | head?: Record; 47 | post?: Record; 48 | put?: Record; 49 | patch?: Record; 50 | }; 51 | } 52 | 53 | declare module "axios" { 54 | interface AxiosRequestConfig { 55 | progress?: boolean; 56 | } 57 | } 58 | 59 | declare module '@nuxt/schema' { 60 | interface NuxtConfig { 61 | ['axios']?: Partial; 62 | } 63 | interface NuxtOptions { 64 | ['axios']?: ModuleOptions; 65 | } 66 | } 67 | 68 | declare const module: NuxtSchema.NuxtModule; 69 | 70 | declare module "#app" { 71 | interface NuxtApp { 72 | $axios: NuxtAxiosInstance; 73 | } 74 | interface NuxtOptions { 75 | axios: ModuleOptions; 76 | } 77 | } 78 | 79 | export { module as default, ModuleOptions, NuxtAxiosInstance }; 80 | -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/src/options.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AxiosError, 3 | AxiosRequestConfig, 4 | AxiosResponse, 5 | AxiosStatic, 6 | } from "axios"; 7 | 8 | import { IAxiosRetryConfig } from "axios-retry"; 9 | 10 | export interface NuxtAxiosInstance extends AxiosStatic { 11 | $request(config: AxiosRequestConfig): Promise; 12 | $get(url: string, config?: AxiosRequestConfig): Promise; 13 | $delete(url: string, config?: AxiosRequestConfig): Promise; 14 | $head(url: string, config?: AxiosRequestConfig): Promise; 15 | $options(url: string, config?: AxiosRequestConfig): Promise; 16 | $post( 17 | url: string, 18 | data?: any, 19 | config?: AxiosRequestConfig 20 | ): Promise; 21 | $put( 22 | url: string, 23 | data?: any, 24 | config?: AxiosRequestConfig 25 | ): Promise; 26 | $patch( 27 | url: string, 28 | data?: any, 29 | config?: AxiosRequestConfig 30 | ): Promise; 31 | 32 | setBaseURL(baseURL: string): void; 33 | setHeader( 34 | name: string, 35 | value?: string | false, 36 | scopes?: string | string[] 37 | ): void; 38 | setToken( 39 | token: string | false, 40 | type?: string, 41 | scopes?: string | string[] 42 | ): void; 43 | 44 | onRequest( 45 | callback: ( 46 | config: AxiosRequestConfig 47 | ) => void | AxiosRequestConfig | Promise 48 | ): void; 49 | onResponse( 50 | callback: ( 51 | response: AxiosResponse 52 | ) => void | AxiosResponse | Promise> 53 | ): void; 54 | onError(callback: (error: AxiosError) => any): void; 55 | onRequestError(callback: (error: AxiosError) => any): void; 56 | onResponseError(callback: (error: AxiosError) => any): void; 57 | 58 | create(options?: AxiosRequestConfig): NuxtAxiosInstance; 59 | } 60 | 61 | export interface ModuleOptions { 62 | baseURL?: string; 63 | baseUrl?: string; 64 | browserBaseURL?: string; 65 | browserBaseUrl?: string; 66 | globalName?: string; 67 | credentials?: boolean; 68 | debug?: boolean; 69 | host?: string; 70 | prefix?: string; 71 | progress?: boolean; 72 | proxyHeaders?: boolean; 73 | proxyHeadersIgnore?: string[]; 74 | proxy?: boolean; 75 | port?: string | number; 76 | retry?: boolean | IAxiosRetryConfig; 77 | https?: boolean; 78 | headers?: { 79 | common?: Record; 80 | delete?: Record; 81 | get?: Record; 82 | head?: Record; 83 | post?: Record; 84 | put?: Record; 85 | patch?: Record; 86 | }; 87 | } 88 | 89 | declare module "axios" { 90 | export interface AxiosRequestConfig { 91 | progress?: boolean; 92 | } 93 | } 94 | 95 | declare module '@nuxt/schema' { 96 | export interface NuxtConfig { 97 | ['axios']?: Partial 98 | } 99 | export interface NuxtOptions { 100 | ['axios']?: ModuleOptions 101 | } 102 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/providers/laravel-passport.ts: -------------------------------------------------------------------------------- 1 | import type { RefreshTokenOptions, TokenOptions, UserOptions, RecursivePartial, ProviderPartialOptions, ProviderOptions } from '../types'; 2 | import type { Oauth2SchemeOptions, RefreshSchemeOptions } from '../runtime'; 3 | import type { Nuxt } from '@nuxt/schema' 4 | import { assignDefaults, addAuthorize, initializePasswordGrantFlow, assignAbsoluteEndpoints } from '../utils/provider'; 5 | 6 | export interface LaravelPassportProviderOptions extends ProviderOptions, Oauth2SchemeOptions { 7 | url: string; 8 | } 9 | 10 | export interface LaravelPassportPasswordProviderOptions extends ProviderOptions, RefreshSchemeOptions { 11 | url: string; 12 | } 13 | 14 | export type PartialPassportOptions = ProviderPartialOptions; 15 | export type PartialPassportPasswordOptions = ProviderPartialOptions; 16 | 17 | function isPasswordGrant(strategy: PartialPassportOptions | PartialPassportPasswordOptions): strategy is PartialPassportPasswordOptions { 18 | return strategy.grantType === 'password'; 19 | } 20 | 21 | export function laravelPassport(nuxt: Nuxt, strategy: PartialPassportOptions | PartialPassportPasswordOptions): void { 22 | const { url } = strategy; 23 | 24 | if (!url) { 25 | throw new Error('url is required is laravel passport!'); 26 | } 27 | 28 | const defaults: RecursivePartial<{ 29 | name: string; 30 | token: TokenOptions; 31 | refreshToken: RefreshTokenOptions; 32 | user: UserOptions; 33 | }> = { 34 | name: 'laravelPassport', 35 | token: { 36 | property: 'access_token', 37 | type: 'Bearer', 38 | name: 'Authorization', 39 | maxAge: 60 * 60 * 24 * 365, 40 | }, 41 | refreshToken: { 42 | property: 'refresh_token', 43 | data: 'refresh_token', 44 | maxAge: 60 * 60 * 24 * 30, 45 | }, 46 | user: { 47 | property: false, 48 | }, 49 | }; 50 | 51 | let DEFAULTS: typeof strategy 52 | 53 | if (isPasswordGrant(strategy)) { 54 | DEFAULTS = { 55 | ...defaults, 56 | scheme: 'refresh', 57 | endpoints: { 58 | token: url + '/oauth/token', 59 | login: { 60 | baseURL: '', 61 | }, 62 | refresh: { 63 | baseURL: '', 64 | }, 65 | logout: false, 66 | user: { 67 | url: url + '/api/auth/user', 68 | }, 69 | }, 70 | grantType: 'password', 71 | }; 72 | 73 | assignDefaults(strategy, DEFAULTS); 74 | 75 | assignAbsoluteEndpoints(strategy); 76 | initializePasswordGrantFlow(nuxt, strategy); 77 | } else { 78 | DEFAULTS = { 79 | ...defaults, 80 | scheme: 'oauth2', 81 | endpoints: { 82 | authorization: url + '/oauth/authorize', 83 | token: url + '/oauth/token', 84 | userInfo: url + '/api/auth/user', 85 | logout: false, 86 | }, 87 | responseType: 'code', 88 | grantType: 'authorization_code', 89 | scope: '*', 90 | }; 91 | 92 | assignDefaults(strategy, DEFAULTS); 93 | 94 | assignAbsoluteEndpoints(strategy); 95 | addAuthorize(nuxt, strategy); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/build.config.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtModule } from '@nuxt/schema' 2 | import { existsSync, promises as fsp } from 'node:fs' 3 | import { defineBuildConfig } from 'unbuild' 4 | import { pathToFileURL } from 'url' 5 | import { resolve } from 'path' 6 | import { fdir } from 'fdir' 7 | import mri from 'mri' 8 | 9 | const args = mri(process.argv.slice(2)) 10 | 11 | export default defineBuildConfig({ 12 | declaration: true, 13 | stub: args.stub, 14 | entries: [ 15 | 'src/module', 16 | { input: 'src/types/', outDir: 'dist/types', ext: 'mjs' }, 17 | { input: 'src/runtime/', outDir: 'dist/runtime', ext: 'mjs' }, 18 | { input: 'src/utils/', outDir: 'dist/utils', ext: 'mjs' }, 19 | { input: 'src/providers/', outDir: 'dist/providers', ext: 'mjs' }, 20 | ], 21 | rollup: { 22 | emitCJS: false, 23 | cjsBridge: true, 24 | }, 25 | externals: [ 26 | '@nuxt/schema', 27 | '@nuxt/schema-edge', 28 | '@nuxt/kit', 29 | '@nuxt/kit-edge', 30 | 'nuxt', 31 | 'nuxt-edge', 32 | 'nuxt3', 33 | 'vue', 34 | 'vue-demi' 35 | ], 36 | hooks: { 37 | async 'rollup:dts:build'(ctx) { 38 | const api = new fdir().withFullPaths().glob('./**/*.mjs').crawl(ctx.options.outDir + '/types').withPromise(); 39 | 40 | api.then((files) => { 41 | // @ts-ignore 42 | files.forEach(async (file: any) => { 43 | await fsp.unlink(file) 44 | }); 45 | }); 46 | }, 47 | async 'rollup:done'(ctx) { 48 | // Generate CommonJS stup 49 | await writeCJSStub(ctx.options.outDir) 50 | 51 | // Load module meta 52 | const moduleEntryPath = resolve(ctx.options.outDir, 'module.mjs') 53 | const moduleFn: NuxtModule = await import( 54 | pathToFileURL(moduleEntryPath).toString() 55 | ).then(r => r.default || r).catch((err) => { 56 | console.error(err) 57 | console.error('Cannot load module. Please check dist:', moduleEntryPath) 58 | return null 59 | }) 60 | if (!moduleFn) { 61 | return 62 | } 63 | const moduleMeta = await moduleFn.getMeta!() 64 | 65 | // Enhance meta using package.json 66 | if (ctx.pkg) { 67 | if (!moduleMeta.name) { 68 | moduleMeta.name = ctx.pkg.name 69 | } 70 | if (!moduleMeta.version) { 71 | moduleMeta.version = ctx.pkg.version 72 | } 73 | } 74 | 75 | // Write meta 76 | const metaFile = resolve(ctx.options.outDir, 'module.json') 77 | await fsp.writeFile(metaFile, JSON.stringify(moduleMeta, null, 2), 'utf8') 78 | } 79 | } 80 | }); 81 | 82 | async function writeCJSStub(distDir: string) { 83 | const cjsStubFile = resolve(distDir, 'module.cjs') 84 | if (existsSync(cjsStubFile)) { 85 | return 86 | } 87 | 88 | const cjsStub = 89 | `module.exports = function(...args) { 90 | return import('./module.mjs').then(m => m.default.call(this, ...args)) 91 | } 92 | 93 | const _meta = module.exports.meta = require('./module.json') 94 | module.exports.getMeta = () => Promise.resolve(_meta)` 95 | 96 | await fsp.writeFile(cjsStubFile, cjsStub, 'utf8') 97 | } 98 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/scheme.ts: -------------------------------------------------------------------------------- 1 | import type { HTTPRequest, HTTPResponse } from '.'; 2 | import type { Auth } from '../runtime/core'; 3 | import type { Token, IdToken, RefreshToken, RefreshController, RequestHandler,} from '../runtime/inc'; 4 | import type { PartialExcept } from './utils'; 5 | 6 | export interface UserOptions { 7 | property: string | false; 8 | autoFetch: boolean; 9 | } 10 | 11 | export interface CookieUserOptions { 12 | property: { 13 | client: string | false; 14 | server: string | false; 15 | }; 16 | autoFetch: boolean; 17 | } 18 | 19 | export interface EndpointsOption { 20 | [endpoint: string]: string | HTTPRequest | false; 21 | } 22 | 23 | // Scheme 24 | 25 | export interface SchemeOptions { 26 | name?: string; 27 | } 28 | 29 | export type SchemePartialOptions = PartialExcept; 30 | 31 | export interface SchemeCheck { 32 | valid: boolean; 33 | tokenExpired?: boolean; 34 | refreshTokenExpired?: boolean; 35 | idTokenExpired?: boolean; 36 | isRefreshable?: boolean; 37 | } 38 | 39 | export interface Scheme { 40 | options: OptionsT; 41 | name?: string; 42 | $auth: Auth; 43 | mounted?(...args: any[]): Promise; 44 | check?(checkStatus: boolean): SchemeCheck; 45 | login(...args: any[]): Promise; 46 | fetchUser(endpoint?: HTTPRequest): Promise; 47 | setUserToken?( 48 | token: string | boolean, 49 | refreshToken?: string | boolean 50 | ): Promise; 51 | logout?(endpoint?: HTTPRequest): Promise | void; 52 | reset?(options?: { resetInterceptor: boolean }): void; 53 | } 54 | 55 | // Token 56 | 57 | export interface TokenOptions { 58 | property: string; 59 | type: string | false; 60 | name: string; 61 | maxAge: number | false; 62 | global: boolean; 63 | required: boolean; 64 | prefix: string; 65 | expirationPrefix: string; 66 | } 67 | 68 | export interface TokenableSchemeOptions extends SchemeOptions { 69 | token?: TokenOptions; 70 | endpoints: EndpointsOption; 71 | } 72 | 73 | export interface TokenableScheme extends Scheme { 74 | token?: Token; 75 | requestHandler: RequestHandler; 76 | } 77 | 78 | // ID Token 79 | 80 | export interface IdTokenableSchemeOptions extends SchemeOptions { 81 | idToken: TokenOptions; 82 | } 83 | 84 | export interface IdTokenableScheme extends Scheme { 85 | idToken: IdToken; 86 | requestHandler: RequestHandler; 87 | } 88 | 89 | // Refrash 90 | 91 | export interface RefreshTokenOptions { 92 | property: string | false; 93 | type: string | false; 94 | data: string | false; 95 | maxAge: number | false; 96 | required: boolean; 97 | tokenRequired: boolean; 98 | prefix: string; 99 | expirationPrefix: string; 100 | } 101 | 102 | export interface RefreshableSchemeOptions extends TokenableSchemeOptions { 103 | refreshToken: RefreshTokenOptions; 104 | } 105 | 106 | export interface RefreshableScheme extends TokenableScheme { 107 | refreshToken: RefreshToken; 108 | refreshController: RefreshController; 109 | refreshTokens(): Promise; 110 | } 111 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/types/scheme.d.ts: -------------------------------------------------------------------------------- 1 | import type { HTTPRequest, HTTPResponse } from "../types"; 2 | import type { Auth } from "../runtime/core"; 3 | import type { Token, IdToken, RefreshToken, RefreshController, RequestHandler,} from "../runtime/inc"; 4 | import type { PartialExcept } from "./utils"; 5 | 6 | export interface UserOptions { 7 | property: string | false; 8 | autoFetch: boolean; 9 | } 10 | 11 | export interface CookieUserOptions { 12 | property: { 13 | client: string | false; 14 | server: string | false; 15 | }; 16 | autoFetch: boolean; 17 | } 18 | 19 | export interface EndpointsOption { 20 | [endpoint: string]: string | HTTPRequest | false; 21 | } 22 | 23 | // Scheme 24 | 25 | export interface SchemeOptions { 26 | name?: string; 27 | } 28 | 29 | export type SchemePartialOptions = PartialExcept; 30 | 31 | export interface SchemeCheck { 32 | valid: boolean; 33 | tokenExpired?: boolean; 34 | refreshTokenExpired?: boolean; 35 | idTokenExpired?: boolean; 36 | isRefreshable?: boolean; 37 | } 38 | 39 | export interface Scheme { 40 | options: OptionsT; 41 | name?: string; 42 | $auth: Auth; 43 | mounted?(...args: any[]): Promise; 44 | check?(checkStatus: boolean): SchemeCheck; 45 | login(...args: any[]): Promise; 46 | fetchUser(endpoint?: HTTPRequest): Promise; 47 | setUserToken?( 48 | token: string | boolean, 49 | refreshToken?: string | boolean 50 | ): Promise; 51 | logout?(endpoint?: HTTPRequest): Promise | void; 52 | reset?(options?: { resetInterceptor: boolean }): void; 53 | } 54 | 55 | // Token 56 | 57 | export interface TokenOptions { 58 | property: string; 59 | type: string | false; 60 | name: string; 61 | maxAge: number | false; 62 | global: boolean; 63 | required: boolean; 64 | prefix: string; 65 | expirationPrefix: string; 66 | } 67 | 68 | export interface TokenableSchemeOptions extends SchemeOptions { 69 | token?: TokenOptions; 70 | endpoints: EndpointsOption; 71 | } 72 | 73 | export interface TokenableScheme extends Scheme { 74 | token?: Token; 75 | requestHandler: RequestHandler; 76 | } 77 | 78 | // ID Token 79 | 80 | export interface IdTokenableSchemeOptions extends SchemeOptions { 81 | idToken: TokenOptions; 82 | } 83 | 84 | export interface IdTokenableScheme extends Scheme { 85 | idToken: IdToken; 86 | requestHandler: RequestHandler; 87 | } 88 | 89 | // Refrash 90 | 91 | export interface RefreshTokenOptions { 92 | property: string | false; 93 | type: string | false; 94 | data: string | false; 95 | maxAge: number | false; 96 | required: boolean; 97 | tokenRequired: boolean; 98 | prefix: string; 99 | expirationPrefix: string; 100 | } 101 | 102 | export interface RefreshableSchemeOptions extends TokenableSchemeOptions { 103 | refreshToken: RefreshTokenOptions; 104 | } 105 | 106 | export interface RefreshableScheme extends TokenableScheme { 107 | refreshToken: RefreshToken; 108 | refreshController: RefreshController; 109 | refreshTokens(): Promise; 110 | } 111 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/refresh-token.ts: -------------------------------------------------------------------------------- 1 | import type { RefreshableScheme } from '../../types'; 2 | import type { Storage } from '../core'; 3 | import { addTokenPrefix } from '../../utils'; 4 | import { TokenStatus } from './token-status'; 5 | import jwtDecode, { JwtPayload } from 'jwt-decode'; 6 | 7 | export class RefreshToken { 8 | scheme: RefreshableScheme; 9 | $storage: Storage; 10 | 11 | constructor(scheme: RefreshableScheme, storage: Storage) { 12 | this.scheme = scheme; 13 | this.$storage = storage; 14 | } 15 | 16 | get(): string | boolean { 17 | const key = this.scheme.options.refreshToken.prefix + this.scheme.name; 18 | 19 | return this.$storage.getUniversal(key) as string | boolean; 20 | } 21 | 22 | set(tokenValue: string | boolean): string | boolean { 23 | const refreshToken = addTokenPrefix(tokenValue, this.scheme.options.refreshToken.type); 24 | 25 | this.#setToken(refreshToken); 26 | this.#updateExpiration(refreshToken); 27 | 28 | return refreshToken; 29 | } 30 | 31 | sync(): string | boolean { 32 | const refreshToken = this.#syncToken(); 33 | this.#syncExpiration(); 34 | 35 | return refreshToken; 36 | } 37 | 38 | reset(): void { 39 | this.#setToken(false); 40 | this.#setExpiration(false); 41 | } 42 | 43 | status(): TokenStatus { 44 | return new TokenStatus(this.get(), this.#getExpiration()); 45 | } 46 | 47 | #getExpiration(): number | false { 48 | const key = this.scheme.options.refreshToken.expirationPrefix + this.scheme.name; 49 | 50 | return this.$storage.getUniversal(key) as number | false; 51 | } 52 | 53 | #setExpiration(expiration: number | false): number | false { 54 | const key = this.scheme.options.refreshToken.expirationPrefix + this.scheme.name; 55 | 56 | return this.$storage.setUniversal(key, expiration) as number | false; 57 | } 58 | 59 | #syncExpiration(): number | false { 60 | const key = this.scheme.options.refreshToken.expirationPrefix + this.scheme.name; 61 | 62 | return this.$storage.syncUniversal(key) as number | false; 63 | } 64 | 65 | #updateExpiration(refreshToken: string | boolean): number | false | void { 66 | let refreshTokenExpiration: number; 67 | const tokenIssuedAtMillis = Date.now(); 68 | const tokenTTLMillis = Number(this.scheme.options.refreshToken.maxAge) * 1000; 69 | const tokenExpiresAtMillis = tokenTTLMillis ? tokenIssuedAtMillis + tokenTTLMillis : 0; 70 | 71 | try { 72 | refreshTokenExpiration = jwtDecode(refreshToken as string).exp! * 1000 || tokenExpiresAtMillis; 73 | } catch (error: any) { 74 | // If the token is not jwt, we can't decode and refresh it, use tokenExpiresAt value 75 | refreshTokenExpiration = tokenExpiresAtMillis; 76 | 77 | if (!((error && error.name === 'InvalidTokenError'))) { 78 | throw error; 79 | } 80 | } 81 | 82 | // Set token expiration 83 | return this.#setExpiration(refreshTokenExpiration || false); 84 | } 85 | 86 | #setToken(refreshToken: string | boolean): string | boolean { 87 | const key = this.scheme.options.refreshToken.prefix + this.scheme.name; 88 | 89 | return this.$storage.setUniversal(key, refreshToken) as string | boolean; 90 | } 91 | 92 | #syncToken(): string | boolean { 93 | const key = this.scheme.options.refreshToken.prefix + this.scheme.name; 94 | 95 | return this.$storage.syncUniversal(key) as string | boolean; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/module.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleOptions } from './types'; 2 | import { addImports, addPluginTemplate, addTemplate, createResolver, defineNuxtModule, installModule } from '@nuxt/kit'; 3 | import { name, version } from '../package.json'; 4 | import { resolveStrategies } from './resolve'; 5 | import { moduleDefaults } from './options'; 6 | import { getAuthDTS, getAuthPlugin } from './plugin'; 7 | import { defu } from 'defu'; 8 | 9 | const CONFIG_KEY = 'auth'; 10 | 11 | export default defineNuxtModule({ 12 | meta: { 13 | name, 14 | version, 15 | configKey: CONFIG_KEY, 16 | compatibility: { 17 | nuxt: '^3.0.0', 18 | }, 19 | }, 20 | defaults: moduleDefaults, 21 | async setup(moduleOptions, nuxt) { 22 | // Merge all option sources 23 | const options: ModuleOptions = defu({ ...moduleOptions, ...nuxt.options.runtimeConfig[CONFIG_KEY] }, moduleDefaults) 24 | 25 | // Resolver 26 | const resolver = createResolver(import.meta.url); 27 | 28 | // Resolve strategies 29 | const { strategies, strategyScheme } = await resolveStrategies(nuxt, options); 30 | delete options.strategies; 31 | 32 | // Resolve required imports 33 | const uniqueImports = new Set(); 34 | const schemeImports = Object.values(strategyScheme).filter((i) => { 35 | if (uniqueImports.has(i.as)) { 36 | return false; 37 | } 38 | 39 | uniqueImports.add(i.as); 40 | return true; 41 | }); 42 | 43 | // Set defaultStrategy 44 | options.defaultStrategy = options.defaultStrategy || strategies.length ? strategies[0].name : ''; 45 | 46 | // Install http module if not in modules 47 | if (!nuxt.options.modules.includes('@nuxtjs-alt/http')) { 48 | installModule('@nuxtjs-alt/http') 49 | } 50 | 51 | // Add auth plugin 52 | addPluginTemplate({ 53 | getContents: () => getAuthPlugin({ options, strategies, strategyScheme, schemeImports }), 54 | filename: 'auth.plugin.mjs', 55 | }); 56 | 57 | addTemplate({ 58 | getContents: () => getAuthDTS(), 59 | filename: 'auth.plugin.d.ts', 60 | write: true 61 | }) 62 | 63 | // Add auto imports 64 | addImports([ 65 | { from: resolver.resolve('runtime/composables'), name: 'useAuth' }, 66 | ]) 67 | 68 | // Runtime 69 | const runtime = resolver.resolve('runtime'); 70 | nuxt.options.alias['#auth/runtime'] = runtime; 71 | 72 | // Utils 73 | const utils = resolver.resolve('utils'); 74 | nuxt.options.alias['#auth/utils'] = utils; 75 | 76 | // Providers 77 | const providers = resolver.resolve('providers'); 78 | nuxt.options.alias['#auth/providers'] = providers; 79 | 80 | // Transpile 81 | nuxt.options.build.transpile.push(runtime, providers, utils) 82 | 83 | // Middleware 84 | if (options.enableMiddleware) { 85 | nuxt.hook('app:resolve', (app) => { 86 | app.middleware.push({ 87 | name: 'auth', 88 | path: resolver.resolve('runtime/core/middleware'), 89 | global: options.globalMiddleware, 90 | }); 91 | }); 92 | } 93 | 94 | // Extend auth with plugins 95 | if (options.plugins) { 96 | options.plugins.forEach((p) => nuxt.options.plugins.push(p)) 97 | delete options.plugins 98 | } 99 | } 100 | }); -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/id-token.ts: -------------------------------------------------------------------------------- 1 | import decode, { JwtPayload } from 'jwt-decode'; 2 | import { addTokenPrefix } from '../../utils'; 3 | import type { IdTokenableScheme } from '../../types'; 4 | import type { Storage } from '../core'; 5 | import { TokenStatus } from './token-status'; 6 | 7 | export class IdToken { 8 | scheme: IdTokenableScheme; 9 | $storage: Storage; 10 | 11 | constructor(scheme: IdTokenableScheme, storage: Storage) { 12 | this.scheme = scheme; 13 | this.$storage = storage; 14 | } 15 | 16 | get(): string | boolean { 17 | const key = this.scheme.options.idToken.prefix + this.scheme.name; 18 | 19 | return this.$storage.getUniversal(key) as string | boolean; 20 | } 21 | 22 | set(tokenValue: string | boolean): string | boolean { 23 | const idToken = addTokenPrefix(tokenValue, this.scheme.options.idToken.type); 24 | 25 | this.#setToken(idToken); 26 | this.#updateExpiration(idToken); 27 | 28 | return idToken; 29 | } 30 | 31 | sync(): string | boolean { 32 | const idToken = this.#syncToken(); 33 | this.#syncExpiration(); 34 | 35 | return idToken; 36 | } 37 | 38 | reset() { 39 | this.#setToken(false); 40 | this.#setExpiration(false); 41 | } 42 | 43 | status(): TokenStatus { 44 | return new TokenStatus(this.get(), this.#getExpiration()); 45 | } 46 | 47 | #getExpiration(): number | false { 48 | const key = this.scheme.options.idToken.expirationPrefix + this.scheme.name; 49 | 50 | return this.$storage.getUniversal(key) as number | false; 51 | } 52 | 53 | #setExpiration(expiration: number | false): number | false { 54 | const key = this.scheme.options.idToken.expirationPrefix + this.scheme.name; 55 | 56 | return this.$storage.setUniversal(key, expiration) as number | false; 57 | } 58 | 59 | #syncExpiration(): number | false { 60 | const key = 61 | this.scheme.options.idToken.expirationPrefix + this.scheme.name; 62 | 63 | return this.$storage.syncUniversal(key) as number | false; 64 | } 65 | 66 | #updateExpiration(idToken: string | boolean): number | false | void { 67 | let idTokenExpiration: number; 68 | const tokenIssuedAtMillis = Date.now(); 69 | const tokenTTLMillis = Number(this.scheme.options.idToken.maxAge) * 1000; 70 | const tokenExpiresAtMillis = tokenTTLMillis ? tokenIssuedAtMillis + tokenTTLMillis : 0; 71 | 72 | try { 73 | idTokenExpiration = decode(idToken as string).exp! * 1000 || tokenExpiresAtMillis; 74 | } 75 | catch (error: any) { 76 | // If the token is not jwt, we can't decode and refresh it, use tokenExpiresAt value 77 | idTokenExpiration = tokenExpiresAtMillis; 78 | 79 | if (!(error && error.name === 'InvalidTokenError')) { 80 | throw error; 81 | } 82 | } 83 | 84 | // Set token expiration 85 | return this.#setExpiration(idTokenExpiration || false); 86 | } 87 | 88 | #setToken(idToken: string | boolean): string | boolean { 89 | const key = this.scheme.options.idToken.prefix + this.scheme.name; 90 | 91 | return this.$storage.setUniversal(key, idToken) as string | boolean; 92 | } 93 | 94 | #syncToken(): string | boolean { 95 | const key = this.scheme.options.idToken.prefix + this.scheme.name; 96 | 97 | return this.$storage.syncUniversal(key) as string | boolean; 98 | } 99 | 100 | userInfo() { 101 | const idToken = this.get(); 102 | if (typeof idToken === 'string') { 103 | return decode(idToken); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /@nuxtjs-alt/http/readme.md: -------------------------------------------------------------------------------- 1 | **Information** 2 | 3 | This serves as an extension to ohmyfetch for nuxt. Please note this is only for nuxt3. 4 | This works similar to nuxt/http and nuxtjs-alt/axios except it utilizes ohmyfetch. All property options will be under `http`. 5 | This module is required in order for `@nuxtjs-alt/auth` to function. 6 | 7 | Remember this is a mix of `ofetch` and `nuxt/http` so to use methods you would use as an example: 8 | 9 | ```ts 10 | // Available methods: 'get', 'head', 'delete', 'post', 'put', 'patch', 'options' 11 | 12 | // $http.$get('/api', options) and $http.$get({ url: '/api' }) is the same as $fetch('/api', { method: 'get' }) 13 | await $http.$get('/api', options) 14 | await $http.$get({ url: '/api', ...options }) 15 | 16 | // Access Raw Response 17 | // $http.get('/api', options) and $http.get({ url: '/api' }) is the same as $fetch.raw('/api', { method: 'get' }) 18 | await $http.get('/api', options) 19 | await $http.get({ url: '/api', ...options }) 20 | 21 | // $http.request('/api', options) and $http.request({ url: '/api' }) is the same as $fetch('/api', options) 22 | await $http.request({ url: '/api', ...options }) 23 | await $http.request('/api', options) 24 | 25 | // Access Raw Response 26 | // $http.raw('/api', options) and $http.raw({ url: '/api' }) is the same as $fetch.raw('/api', options) 27 | await $http.raw({ url: '/api', ...options }) 28 | await $http.raw('/api', options) 29 | 30 | // Access Fetch Native Response 31 | // $http.natvie('/api', options) and $http.native({ url: '/api' }) is the same as $fetch.native('/api', options) or fetch('/api', options) 32 | await $http.native({ url: '/api', ...options }) 33 | await $http.native('/api', options) 34 | ``` 35 | 36 | A `useHttp` composable is avaialble, it works like `useFetch` except uses this module under the hood 37 | 38 | **Interceptors** 39 | 40 | The interceptors should work exactly like how axios has it so to access them you would use: 41 | 42 | ```ts 43 | $http.interceptors.request.use(config) 44 | $http.interceptors.response.use(response) 45 | 46 | ``` 47 | 48 | A `interceptorPlugin` property has been added. This relies on the proxy module being present and will proxy urls based on the target for the client. 49 | 50 | @nuxtjs-axios based functions have also been added: 51 | 52 | ```ts 53 | $http.onRequest(config) 54 | $http.onResponse(response) 55 | $http.onRequestError(err) 56 | $http.onResponseError(err) 57 | $http.onError(err) 58 | ``` 59 | 60 | **Config Options** 61 | 62 | ```ts 63 | import { defineNuxtConfig } from 'nuxt/config' 64 | 65 | export default defineNuxtConfig({ 66 | modules: [ 67 | '@nuxtjs-alt/http', 68 | ], 69 | http: { 70 | baseURL: 'localhost:3000', // default is localhost:3000, otherwise it is the HOST/NITRO_HOST and PORT/NITRO_PORT enviromental values 71 | browserBaseURL: undefined, // default is nuxt app baseURL, otherwise if interceptorPlugin is enabled it's based on the proxy urls 72 | proxyHeaders: true, 73 | proxyHeadersIgnore: [ 74 | 'accept', 75 | 'connection', 76 | 'cf-connecting-ip', 77 | 'cf-ray', 78 | 'content-length', 79 | 'content-md5', 80 | 'content-type', 81 | 'host', 82 | 'if-modified-since', 83 | 'if-none-match', 84 | 'x-forwarded-host', 85 | 'x-forwarded-port', 86 | 'x-forwarded-proto' 87 | ], 88 | serverTimeout: 10000, 89 | clientTimeout: 25000, 90 | https: false, 91 | retry: 1, 92 | headers: { 93 | accept: 'application/json, text/plain, */*' 94 | }, 95 | credentials: 'omit', 96 | debug: false, 97 | interceptorPlugin: false 98 | } 99 | }) 100 | ``` 101 | 102 | Please do tell me if you encounter any bugs. -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/token.ts: -------------------------------------------------------------------------------- 1 | import type { JwtPayload } from 'jwt-decode'; 2 | import type { TokenableScheme } from '../../types'; 3 | import type { Storage } from '../core'; 4 | import { addTokenPrefix } from '../../utils'; 5 | import { TokenStatus } from './token-status'; 6 | import decode from 'jwt-decode'; 7 | 8 | export class Token { 9 | scheme: TokenableScheme; 10 | $storage: Storage; 11 | 12 | constructor(scheme: TokenableScheme, storage: Storage) { 13 | this.scheme = scheme; 14 | this.$storage = storage; 15 | } 16 | 17 | get(): string | boolean { 18 | const key = this.scheme.options.token!.prefix + this.scheme.name; 19 | 20 | return this.$storage.getUniversal(key) as string | boolean; 21 | } 22 | 23 | set(tokenValue: string | boolean, expiresIn: number | boolean = false): string | boolean { 24 | const token = addTokenPrefix(tokenValue, this.scheme.options.token!.type); 25 | 26 | this.#setToken(token); 27 | this.#updateExpiration(token, expiresIn); 28 | 29 | if (typeof token === 'string') { 30 | this.scheme.requestHandler.setHeader(token); 31 | } 32 | 33 | return token; 34 | } 35 | 36 | sync(): string | boolean { 37 | const token = this.#syncToken(); 38 | this.#syncExpiration(); 39 | 40 | if (typeof token === 'string') { 41 | this.scheme.requestHandler.setHeader(token); 42 | } 43 | 44 | return token; 45 | } 46 | 47 | reset(): void { 48 | this.scheme.requestHandler.clearHeader(); 49 | this.#setToken(false); 50 | this.#setExpiration(false); 51 | } 52 | 53 | status(): TokenStatus { 54 | return new TokenStatus(this.get(), this.#getExpiration()); 55 | } 56 | 57 | #getExpiration(): number | false { 58 | const key = this.scheme.options.token!.expirationPrefix + this.scheme.name; 59 | 60 | return this.$storage.getUniversal(key) as number | false; 61 | } 62 | 63 | #setExpiration(expiration: number | false): number | false { 64 | const key = this.scheme.options.token!.expirationPrefix + this.scheme.name; 65 | 66 | return this.$storage.setUniversal(key, expiration) as number | false; 67 | } 68 | 69 | #syncExpiration(): number | false { 70 | const key = this.scheme.options.token!.expirationPrefix + this.scheme.name; 71 | 72 | return this.$storage.syncUniversal(key) as number | false; 73 | } 74 | 75 | #updateExpiration(token: string | boolean, expiresIn: number | boolean): number | false | void { 76 | let tokenExpiration: number; 77 | const tokenIssuedAtMillis = Date.now(); 78 | const maxAge = expiresIn ? expiresIn : this.scheme.options.token!.maxAge 79 | const tokenTTLMillis = Number(maxAge) * 1000 80 | const tokenExpiresAtMillis = tokenTTLMillis ? tokenIssuedAtMillis + tokenTTLMillis : 0; 81 | 82 | try { 83 | tokenExpiration = decode(token as string).exp! * 1000 || tokenExpiresAtMillis; 84 | } 85 | catch (error: any) { 86 | // If the token is not jwt, we can't decode and refresh it, use tokenExpiresAt value 87 | tokenExpiration = tokenExpiresAtMillis; 88 | 89 | if (!(error && error.name === 'InvalidTokenError')) { 90 | throw error; 91 | } 92 | } 93 | 94 | // Set token expiration 95 | return this.#setExpiration(tokenExpiration || false); 96 | } 97 | 98 | #setToken(token: string | boolean): string | boolean { 99 | const key = this.scheme.options.token!.prefix + this.scheme.name; 100 | 101 | return this.$storage.setUniversal(key, token) as string | boolean; 102 | } 103 | 104 | #syncToken(): string | boolean { 105 | const key = this.scheme.options.token!.prefix + this.scheme.name; 106 | 107 | return this.$storage.syncUniversal(key) as string | boolean; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/src/runtime/vuetify.ts: -------------------------------------------------------------------------------- 1 | // Composables 2 | import { createDefaults, DefaultsSymbol } from 'vuetify/lib/composables/defaults.mjs' 3 | import { createDisplay, DisplaySymbol } from 'vuetify/lib/composables/display.mjs' 4 | import { createIcons, IconSymbol } from 'vuetify/lib/composables/icons.mjs' 5 | import { createLocale, LocaleSymbol } from 'vuetify/lib/composables/locale.mjs' 6 | import { createTheme, ThemeSymbol } from './theme' 7 | 8 | // Utilities 9 | import { defineComponent, getUid, IN_BROWSER, mergeDeep } from 'vuetify/lib/util/index.mjs' 10 | import { version } from 'vuetify/package.json' 11 | import { nextTick, reactive } from 'vue' 12 | 13 | // Types 14 | import type { App, ComponentPublicInstance, InjectionKey } from 'vue' 15 | import type { VuetifyOptions } from 'vuetify' 16 | import type { NuxtApp } from 'nuxt/app' 17 | 18 | export interface Blueprint extends Omit { } 19 | 20 | interface VueApp extends App { 21 | $nuxt: NuxtApp 22 | } 23 | 24 | export function createVuetify(vuetify: VuetifyOptions = {}) { 25 | const { blueprint, ...rest } = vuetify 26 | const options = mergeDeep(blueprint, rest) 27 | const { 28 | aliases = {}, 29 | components = {}, 30 | directives = {}, 31 | } = options 32 | 33 | const defaults = createDefaults(options.defaults) 34 | const display = createDisplay(options.display, options.ssr) 35 | const theme = createTheme(options.theme) 36 | const icons = createIcons(options.icons) 37 | const locale = createLocale(options.locale) 38 | 39 | const install = (app: VueApp) => { 40 | for (const key in directives) { 41 | app.directive(key, directives[key]) 42 | } 43 | 44 | for (const key in components) { 45 | app.component(key, components[key]) 46 | } 47 | 48 | for (const key in aliases) { 49 | app.component(key, defineComponent({ 50 | ...aliases[key], 51 | name: key, 52 | aliasName: aliases[key].name, 53 | })) 54 | } 55 | 56 | theme.install(app) 57 | 58 | app.provide(DefaultsSymbol, defaults) 59 | app.provide(DisplaySymbol, display) 60 | app.provide(ThemeSymbol, theme) 61 | app.provide(IconSymbol, icons) 62 | app.provide(LocaleSymbol, locale) 63 | 64 | if (IN_BROWSER && options.ssr) { 65 | if (app.$nuxt) { 66 | app.$nuxt.hook('app:suspense:resolve', () => { 67 | display.update() 68 | }) 69 | } else { 70 | const { mount } = app 71 | app.mount = (...args) => { 72 | const vm = mount(...args) 73 | nextTick(() => display.update()) 74 | app.mount = mount 75 | return vm 76 | } 77 | } 78 | } 79 | 80 | getUid.reset() 81 | 82 | app.mixin({ 83 | computed: { 84 | $vuetify() { 85 | return reactive({ 86 | defaults: inject.call(this, DefaultsSymbol), 87 | display: inject.call(this, DisplaySymbol), 88 | theme: inject.call(this, ThemeSymbol), 89 | icons: inject.call(this, IconSymbol), 90 | locale: inject.call(this, LocaleSymbol), 91 | }) 92 | }, 93 | }, 94 | }) 95 | } 96 | 97 | return { 98 | install, 99 | defaults, 100 | display, 101 | theme, 102 | icons, 103 | locale, 104 | } 105 | } 106 | 107 | createVuetify.version = version 108 | 109 | // Vue's inject() can only be used in setup 110 | function inject(this: ComponentPublicInstance, key: InjectionKey | string) { 111 | const vm = this.$ 112 | 113 | // @ts-ignore 114 | const provides = vm.parent?.provides ?? vm.vnode.appContext?.provides 115 | 116 | if (provides && (key as any) in provides) { 117 | return provides[(key as string)] 118 | } 119 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/resolve.ts: -------------------------------------------------------------------------------- 1 | import type { Strategy, ModuleOptions } from './types'; 2 | import type { Nuxt, NuxtModule } from '@nuxt/schema' 3 | import { resolvePath, requireModule } from '@nuxt/kit'; 4 | import { ProviderAliases } from './providers'; 5 | import * as AUTH_PROVIDERS from './providers'; 6 | import { existsSync } from 'fs'; 7 | import { hash } from 'ohash' 8 | 9 | const BuiltinSchemes = { 10 | local: 'LocalScheme', 11 | cookie: 'CookieScheme', 12 | oauth2: 'Oauth2Scheme', 13 | openIDConnect: 'OpenIDConnectScheme', 14 | refresh: 'RefreshScheme', 15 | laravelJWT: 'LaravelJWTScheme', 16 | auth0: 'Auth0Scheme', 17 | }; 18 | 19 | export interface ImportOptions { 20 | name: string; 21 | as: string; 22 | from: string; 23 | } 24 | 25 | export async function resolveStrategies(nuxt: Nuxt, options: ModuleOptions): Promise<{ strategies: Strategy[]; strategyScheme: Record; }> { 26 | const strategies: Strategy[] = []; 27 | const strategyScheme = {} as Record; 28 | 29 | for (const name of Object.keys(options.strategies!)) { 30 | if (!options.strategies![name] || (options.strategies as Strategy)[name].enabled === false) { 31 | continue; 32 | } 33 | 34 | // Clone strategy 35 | const strategy = Object.assign({}, options.strategies![name]) as Strategy; 36 | 37 | // Default name 38 | if (!strategy.name) { 39 | strategy.name = name; 40 | } 41 | 42 | // Default provider (same as name) 43 | if (!strategy.provider) { 44 | strategy.provider = strategy.name; 45 | } 46 | 47 | // Try to resolve provider 48 | const provider: (...args: any) => any = resolveProvider(strategy.provider); 49 | 50 | delete strategy.provider; 51 | 52 | if (typeof provider === 'function' && !(provider as NuxtModule).getOptions) { 53 | provider(nuxt, strategy); 54 | } 55 | 56 | // Default scheme (same as name) 57 | if (!strategy.scheme) { 58 | strategy.scheme = strategy.name; 59 | } 60 | 61 | try { 62 | // Resolve and keep scheme needed for strategy 63 | const schemeImport = await resolveScheme(strategy.scheme); 64 | delete strategy.scheme; 65 | strategyScheme[strategy.name] = schemeImport as ImportOptions; 66 | 67 | // Add strategy to array 68 | strategies.push(strategy); 69 | } catch (e) { 70 | console.error(`[auth] Error resolving strategy ${strategy.name}: ${e}`); 71 | } 72 | } 73 | 74 | return { 75 | strategies, 76 | strategyScheme, 77 | }; 78 | } 79 | 80 | export async function resolveScheme(scheme: string): Promise { 81 | if (typeof scheme !== 'string') { 82 | return; 83 | } 84 | 85 | if (BuiltinSchemes[scheme as keyof typeof BuiltinSchemes]) { 86 | return { 87 | name: BuiltinSchemes[scheme as keyof typeof BuiltinSchemes], 88 | as: BuiltinSchemes[scheme as keyof typeof BuiltinSchemes], 89 | from: '#auth/runtime', 90 | }; 91 | } 92 | 93 | const path = await resolvePath(scheme); 94 | 95 | if (existsSync(path)) { 96 | const _path = path.replace(/\\/g, '/'); 97 | return { 98 | name: 'default', 99 | as: 'Scheme$' + hash({ path: _path }), 100 | from: _path, 101 | }; 102 | } 103 | } 104 | 105 | export function resolveProvider(provider: string | ((...args: any[]) => any)) { 106 | if (typeof provider === 'function') { 107 | return provider; 108 | } 109 | 110 | if (typeof provider !== 'string') { 111 | return; 112 | } 113 | 114 | provider = (ProviderAliases[provider as keyof typeof ProviderAliases] || provider); 115 | 116 | if (AUTH_PROVIDERS[provider as keyof typeof AUTH_PROVIDERS]) { 117 | return AUTH_PROVIDERS[provider as keyof typeof AUTH_PROVIDERS]; 118 | } 119 | 120 | try { 121 | const m = requireModule(provider); 122 | return m.default || m; 123 | } catch (e) { 124 | return; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /@nuxtjs-alt/vuetify/src/module.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleOptions } from './types'; 2 | import type { Nuxt } from '@nuxt/schema' 3 | import { name, version } from "../package.json"; 4 | import { defineNuxtModule, addPluginTemplate, createResolver, addImports, addTemplate } from '@nuxt/kit'; 5 | import vuetify from 'vite-plugin-vuetify' 6 | import { defu } from 'defu' 7 | 8 | const CONFIG_KEY = 'vuetify' 9 | 10 | export default defineNuxtModule({ 11 | meta: { 12 | name, 13 | version, 14 | configKey: CONFIG_KEY, 15 | compatibility: { 16 | nuxt: '^3.0.0' 17 | } 18 | }, 19 | defaults: { 20 | vuetifyOptions: {}, 21 | pluginOptions: {}, 22 | } as ModuleOptions, 23 | async setup(moduleOptions, nuxt) { 24 | const options: ModuleOptions = moduleOptions 25 | 26 | const { resolve } = createResolver(import.meta.url) 27 | 28 | options.pluginOptions!.styles = options.pluginOptions?.styles ?? true 29 | 30 | if (typeof options.pluginOptions?.styles === 'string' && ['sass', 'expose'].includes(options.pluginOptions.styles)) { 31 | nuxt.options.css.unshift('vuetify/styles/main.sass') 32 | } 33 | else if (options.pluginOptions?.styles === true) { 34 | nuxt.options.css.unshift('vuetify/styles') 35 | } 36 | 37 | // Transpile Vuetify 38 | nuxt.options.build.transpile.push(CONFIG_KEY) 39 | 40 | nuxt.hook('vite:extendConfig', (config) => { 41 | config.optimizeDeps = defu(config.optimizeDeps, { 42 | exclude: ['vuetify'] 43 | }) 44 | 45 | // Vuetify plugin configuration 46 | config.plugins = [ 47 | ...(config.plugins || []), 48 | vuetify(options.pluginOptions), 49 | ] 50 | 51 | config.define = { 52 | ...(config.define || {}), 53 | 'process.env.DEBUG': false, 54 | } 55 | 56 | // @ts-ignore: name property is there but not in the typing 57 | const vueIndex = config.plugins.findIndex((plugin) => plugin.name === 'vite:vue') 58 | if (vueIndex !== -1) { 59 | const vuePlugin = config.plugins.splice(vueIndex, 1)[0] 60 | config.plugins.unshift(vuePlugin) 61 | } 62 | 63 | config.ssr = config.ssr || {} 64 | config.ssr.noExternal = Array.isArray(config.ssr!.noExternal) ? config.ssr.noExternal : [] 65 | config.ssr.noExternal.push(CONFIG_KEY) 66 | }) 67 | 68 | const selectedIcon = options.vuetifyOptions?.icons?.defaultSet ?? 'mdi' 69 | 70 | if (Object.hasOwn(cdnPresets, selectedIcon)) { 71 | setupIcons(nuxt, selectedIcon as IconPreset) 72 | } 73 | 74 | addPluginTemplate({ 75 | src: resolve('./runtime/templates/plugin.mjs'), 76 | filename: 'vuetify.plugin.mjs', 77 | options: { 78 | options: options.vuetifyOptions 79 | } 80 | }) 81 | 82 | // Runtime 83 | const runtime = resolve('./runtime/vuetify'); 84 | nuxt.options.alias['#vuetify'] = runtime; 85 | 86 | // vuetify-specific composables 87 | addImports([ 88 | { from: resolve('./runtime/theme'), name: 'useTheme' }, 89 | { from: CONFIG_KEY, name: 'useDisplay' }, 90 | { from: CONFIG_KEY, name: 'useRtl' }, 91 | { from: CONFIG_KEY, name: 'useLocale' }, 92 | { from: CONFIG_KEY, name: 'useLayout' } 93 | ]) 94 | } 95 | }) 96 | 97 | type IconPreset = keyof typeof cdnPresets 98 | 99 | const cdnPresets = { 100 | mdi: 'https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css', 101 | md: 'https://fonts.googleapis.com/css?family=Material+Icons', 102 | fa: 'https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@latest/css/all.min.css' 103 | } 104 | 105 | function setupIcons(nuxt: Nuxt, preset: IconPreset) { 106 | if (cdnPresets[preset]) { 107 | nuxt.options.app.head.link = nuxt.options.app.head.link || [] 108 | nuxt.options.app.head.link.push({ 109 | rel: 'stylesheet', 110 | type: 'text/css', 111 | href: cdnPresets[preset] 112 | }) 113 | } 114 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteComponent } from 'vue-router'; 2 | import type { RecursivePartial } from '../types'; 3 | import { useRuntimeConfig, useRoute } from '#imports'; 4 | 5 | export const isUnset = (o: any): boolean => typeof o === 'undefined' || o === null; 6 | 7 | export const isSet = (o: any): boolean => !isUnset(o); 8 | 9 | export function isRelativeURL(u: string) { 10 | return (u && u.length && new RegExp(['^\\/([a-zA-Z0-9@\\-%_~.:]', '[/a-zA-Z0-9@\\-%_~.:]*)?', '([?][^#]*)?(#[^#]*)?$'].join('')).test(u)); 11 | } 12 | 13 | export function routeMeta(key: string, value: string | boolean): boolean { 14 | return useRoute().meta[key] === value 15 | } 16 | 17 | export function getMatchedComponents(matches: unknown[] = []): RouteComponent[][] { 18 | return [ 19 | ...useRoute().matched.map(function (m, index: number) { 20 | return Object.keys(m.components!).map(function (key) { 21 | matches.push(index); 22 | return m.components![key]; 23 | }); 24 | }) 25 | ] 26 | } 27 | 28 | export function normalizePath(path: string = ''): string { 29 | // Remove query string 30 | const config = useRuntimeConfig() 31 | let result = path.split('?')[0]; 32 | 33 | // Remove base path 34 | if (config.app.baseURL) { 35 | result = result.replace(config.app.baseURL, '/'); 36 | } 37 | 38 | // Remove redundant / from the end of path 39 | if (result.charAt(result.length - 1) === '/') { 40 | result = result.slice(0, -1); 41 | } 42 | 43 | // Remove duplicate slashes 44 | result = result.replace(/\/+/g, '/'); 45 | 46 | return result; 47 | } 48 | 49 | export function encodeValue(val: any): string { 50 | if (typeof val === 'string') { 51 | return val; 52 | } 53 | return JSON.stringify(val); 54 | } 55 | 56 | export function decodeValue(val: any): any { 57 | // Try to parse as json 58 | if (typeof val === 'string') { 59 | try { 60 | return JSON.parse(val); 61 | } catch (_) {} 62 | } 63 | 64 | // Return as is 65 | return val; 66 | } 67 | 68 | /** 69 | * Get property defined by dot notation in string. 70 | * Based on https://github.com/dy/dotprop (MIT) 71 | * 72 | * @param { Object } holder Target object where to look property up 73 | * @param { string } propName Dot notation, like 'this.a.b.c' 74 | * @return { * } A property value 75 | */ 76 | export function getProp(holder: any, propName: string | false): any { 77 | if (!propName || !holder || typeof holder !== 'object') { 78 | return holder; 79 | } 80 | 81 | if (propName in holder) { 82 | return holder[propName]; 83 | } 84 | 85 | const propParts = Array.isArray(propName) ? propName : (propName as string).split('.'); 86 | 87 | let result = holder; 88 | while (propParts.length && result) { 89 | result = result[propParts.shift()]; 90 | } 91 | 92 | return result; 93 | } 94 | 95 | // Ie 'Bearer ' + token 96 | export function addTokenPrefix(token: string | boolean, tokenType: string | false): string | boolean { 97 | if (!token || !tokenType || typeof token !== 'string' || token.startsWith(tokenType)) { 98 | return token; 99 | } 100 | 101 | return tokenType + ' ' + token; 102 | } 103 | 104 | export function removeTokenPrefix(token: string | boolean, tokenType: string | false): string | boolean { 105 | if (!token || !tokenType || typeof token !== 'string') { 106 | return token; 107 | } 108 | 109 | return token.replace(tokenType + ' ', ''); 110 | } 111 | 112 | export function cleanObj>(obj: T): RecursivePartial { 113 | for (const key in obj) { 114 | if (obj[key] === undefined) { 115 | delete obj[key]; 116 | } 117 | } 118 | 119 | return obj as RecursivePartial; 120 | } 121 | 122 | export function randomString(length: number) { 123 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 124 | let result = ''; 125 | const charactersLength = characters.length; 126 | 127 | for (let i = 0; i < length; i++) { 128 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 129 | } 130 | 131 | return result; 132 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/src/module.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleOptions } from './types' 2 | import type { Nuxt } from '@nuxt/schema' 3 | import { defineNuxtModule, addPluginTemplate, createResolver } from '@nuxt/kit' 4 | import { name, version } from '../package.json' 5 | import { defu } from 'defu' 6 | 7 | const CONFIG_KEY = 'axios' 8 | 9 | export * from './types' 10 | 11 | export default defineNuxtModule({ 12 | meta: { 13 | name, 14 | version, 15 | configKey: CONFIG_KEY, 16 | compatibility: { 17 | nuxt: '^3.0.0-rc.9' 18 | }, 19 | }, 20 | defaults: {} as ModuleOptions, 21 | setup(opts: ModuleOptions, nuxt: Nuxt) { 22 | // Combine options with runtime config 23 | const moduleOptions: ModuleOptions = defu(nuxt.options.runtimeConfig?.public[CONFIG_KEY], opts) 24 | 25 | // Default port 26 | const defaultPort = process.env.API_PORT || moduleOptions.port || process.env.PORT || process.env.npm_package_config_nuxt_port || 3000 27 | 28 | // Default host 29 | let defaultHost = process.env.API_HOST || moduleOptions.host || process.env.HOST || process.env.npm_package_config_nuxt_host || 'localhost' 30 | 31 | if (defaultHost === '0.0.0.0') { 32 | defaultHost = 'localhost' 33 | } 34 | 35 | // Default prefix 36 | const prefix = process.env.API_PREFIX || moduleOptions.prefix || '/' 37 | 38 | // Support baseUrl alternative 39 | if (moduleOptions.baseUrl) { 40 | moduleOptions.baseURL = moduleOptions.baseUrl 41 | delete moduleOptions.baseUrl 42 | } 43 | 44 | if (moduleOptions.browserBaseUrl) { 45 | moduleOptions.browserBaseURL = moduleOptions.browserBaseUrl 46 | delete moduleOptions.browserBaseUrl 47 | } 48 | 49 | // Apply defaults 50 | const options: ModuleOptions = defu(moduleOptions, { 51 | baseURL: `http://${defaultHost}:${defaultPort}${prefix}`, 52 | browserBaseURL: undefined, 53 | credentials: false, 54 | debug: false, 55 | progress: true, 56 | proxyHeaders: true, 57 | proxyHeadersIgnore: [ 58 | 'accept', 59 | 'cf-connecting-ip', 60 | 'cf-ray', 61 | 'content-length', 62 | 'content-md5', 63 | 'content-type', 64 | 'host', 65 | 'if-modified-since', 66 | 'if-none-match', 67 | 'x-forwarded-host', 68 | 'x-forwarded-port', 69 | 'x-forwarded-proto' 70 | ], 71 | proxy: false, 72 | retry: false, 73 | https: false, 74 | headers: { 75 | common: { 76 | accept: 'application/json, text/plain, */*' 77 | }, 78 | delete: {}, 79 | get: {}, 80 | head: {}, 81 | post: {}, 82 | put: {}, 83 | patch: {} 84 | }, 85 | }) 86 | 87 | if (process.env.API_URL) { 88 | options.baseURL = process.env.API_URL 89 | } 90 | 91 | if (process.env.API_URL_BROWSER) { 92 | options.browserBaseURL = process.env.API_URL_BROWSER 93 | } 94 | 95 | if (typeof options.browserBaseURL === 'undefined') { 96 | options.browserBaseURL = options.baseURL 97 | } 98 | 99 | // Normalize options 100 | if (options.retry === true) { 101 | options.retry = {} 102 | } 103 | 104 | // Convert http:// to https:// if https option is on 105 | if (options.https === true) { 106 | const https = (s: string) => s.replace('http://', 'https://') 107 | options.baseURL = https(options.baseURL) 108 | options.browserBaseURL = https(options.browserBaseURL) 109 | } 110 | 111 | // globalName 112 | options.globalName = nuxt.options.globalName || 'nuxt' 113 | 114 | // Set _AXIOS_BASE_URL_ for dynamic SSR baseURL 115 | process.env._AXIOS_BASE_URL_ = options.baseURL 116 | 117 | // resolver 118 | const resolver = createResolver(import.meta.url) 119 | 120 | // Register plugin 121 | addPluginTemplate({ 122 | src: resolver.resolve('runtime/templates/axios.plugin.mjs'), 123 | options: options 124 | }) 125 | 126 | console.debug(`baseURL: ${options.baseURL}`) 127 | console.debug(`browserBaseURL: ${options.browserBaseURL}`) 128 | } 129 | }) -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/request-handler.ts: -------------------------------------------------------------------------------- 1 | import type { TokenableScheme, RefreshableScheme } from '../../types'; 2 | import { ExpiredAuthSessionError } from './expired-auth-session-error'; 3 | import { FetchInstance, FetchConfig } from '@refactorjs/ofetch' 4 | 5 | export class RequestHandler { 6 | scheme: TokenableScheme | RefreshableScheme; 7 | http: FetchInstance; 8 | interceptor: number | undefined | null; 9 | 10 | constructor(scheme: TokenableScheme | RefreshableScheme, http: FetchInstance) { 11 | this.scheme = scheme; 12 | this.http = http; 13 | this.interceptor = null; 14 | } 15 | 16 | setHeader(token: string): void { 17 | if (this.scheme.options.token && this.scheme.options.token.global) { 18 | // Set Authorization token for all fetch requests 19 | this.http.setHeader(this.scheme.options.token.name, token); 20 | } 21 | } 22 | 23 | clearHeader(): void { 24 | if (this.scheme.options.token && this.scheme.options.token.global) { 25 | // Clear Authorization token for all fetch requests 26 | this.http.setHeader(this.scheme.options.token.name, null); 27 | } 28 | } 29 | 30 | initializeRequestInterceptor(refreshEndpoint?: string | Request): void { 31 | this.interceptor = this.http.interceptors.request.use( 32 | async (config: FetchConfig) => { 33 | // Don't intercept refresh token requests 34 | if ((this.scheme.options.token && !this.#needToken(config)) || config.url === refreshEndpoint) { 35 | return config; 36 | } 37 | 38 | // Perform scheme checks. 39 | const { valid, tokenExpired, refreshTokenExpired, isRefreshable } = this.scheme.check!(true); 40 | let isValid = valid; 41 | 42 | // Refresh token has expired. There is no way to refresh. Force reset. 43 | if (refreshTokenExpired) { 44 | this.scheme.reset!(); 45 | throw new ExpiredAuthSessionError(); 46 | } 47 | 48 | // Token has expired. 49 | if (tokenExpired) { 50 | // Refresh token is not available. Force reset. 51 | if (!isRefreshable) { 52 | this.scheme.reset!(); 53 | throw new ExpiredAuthSessionError(); 54 | } 55 | 56 | // Refresh token is available. Attempt refresh. 57 | isValid = await (this.scheme as RefreshableScheme).refreshController 58 | .handleRefresh() 59 | .then(() => true) 60 | .catch(() => { 61 | // Tokens couldn't be refreshed. Force reset. 62 | this.scheme.reset!(); 63 | throw new ExpiredAuthSessionError(); 64 | }); 65 | } 66 | 67 | // Sync token 68 | const token = this.scheme.token; 69 | 70 | // Scheme checks were performed, but returned that is not valid. 71 | if (!isValid) { 72 | // The authorization header in the current request is expired. 73 | // Token was deleted right before this request 74 | if (token && !token.get() && this.#requestHasAuthorizationHeader(config)) { 75 | throw new ExpiredAuthSessionError(); 76 | } 77 | 78 | return config; 79 | } 80 | 81 | // Token is valid, let the request pass 82 | // Fetch updated token and add to current request 83 | return this.#getUpdatedRequestConfig(config, token ? token.get() : false); 84 | } 85 | ); 86 | } 87 | 88 | reset(): void { 89 | // Eject request interceptor 90 | this.http.interceptors.request.eject(this.interceptor as number); 91 | this.interceptor = null; 92 | } 93 | 94 | #needToken(config: FetchConfig): boolean { 95 | const options = this.scheme.options; 96 | 97 | return (options.token!.global || Object.values(options.endpoints).some((endpoint) => typeof endpoint === 'object' ? endpoint.url === config.url : endpoint === config.url)); 98 | } 99 | 100 | // --------------------------------------------------------------- 101 | // Watch requests for token expiration 102 | // Refresh tokens if token has expired 103 | 104 | #getUpdatedRequestConfig(config: FetchConfig, token: string | boolean) { 105 | if (typeof token === 'string') { 106 | config.headers![this.scheme.options.token!.name] = token; 107 | } 108 | 109 | return config; 110 | } 111 | 112 | #requestHasAuthorizationHeader(config: FetchConfig): boolean { 113 | return !!config.headers![this.scheme.options.token!.name]; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /@nuxtjs-alt/http/src/runtime/composables.ts: -------------------------------------------------------------------------------- 1 | import type { FetchConfig } from '@refactorjs/ofetch' 2 | import type { TypedInternalResponse, NitroFetchRequest } from 'nitropack' 3 | //@ts-ignore 4 | import type { AsyncDataOptions, _Transform, KeyOfRes, AsyncData, PickFrom } from '#app' 5 | import { computed, isRef, Ref } from 'vue' 6 | import { useAsyncData, useNuxtApp } from '#imports' 7 | import { hash } from 'ohash' 8 | 9 | export type FetchResult = TypedInternalResponse 10 | 11 | type ComputedOptions> = { 12 | [K in keyof T]: T[K] extends Function ? T[K] : T[K] extends Record ? ComputedOptions | Ref | T[K] : Ref | T[K] 13 | } 14 | 15 | type ComputedFetchOptions = ComputedOptions 16 | 17 | export interface UseHttpOptions< 18 | DataT, 19 | Transform extends _Transform = _Transform, 20 | PickKeys extends KeyOfRes = KeyOfRes 21 | > extends AsyncDataOptions, ComputedFetchOptions { 22 | key?: string 23 | } 24 | 25 | export function useHttp< 26 | ResT = void, 27 | ErrorT = Error, 28 | ReqT extends NitroFetchRequest = NitroFetchRequest, 29 | _ResT = ResT extends void ? FetchResult : ResT, 30 | Transform extends (res: _ResT) => any = (res: _ResT) => _ResT, 31 | PickKeys extends KeyOfRes = KeyOfRes 32 | >( 33 | request: Ref | ReqT | (() => ReqT), 34 | opts?: UseHttpOptions<_ResT, Transform, PickKeys> 35 | ): AsyncData, PickKeys>, ErrorT | null | true> 36 | export function useHttp< 37 | ResT = void, 38 | ErrorT = Error, 39 | ReqT extends NitroFetchRequest = NitroFetchRequest, 40 | _ResT = ResT extends void ? FetchResult : ResT, 41 | Transform extends (res: _ResT) => any = (res: _ResT) => _ResT, 42 | PickKeys extends KeyOfRes = KeyOfRes 43 | >( 44 | request: Ref | ReqT | (() => ReqT), 45 | opts: UseHttpOptions<_ResT, Transform, PickKeys> = {} 46 | ) { 47 | if (!request) { 48 | throw new Error('[nuxt] [useHttp] request is missing.') 49 | } 50 | 51 | const key = (opts.key || '$h' + hash([request, { ...opts, transform: null }])) 52 | 53 | const _request = computed(() => { 54 | let r = request 55 | if (typeof r === 'function') { 56 | r = r() 57 | } 58 | return (isRef(r) ? r.value : r) 59 | }) 60 | 61 | const { 62 | server, 63 | lazy, 64 | default: defaultFn, 65 | transform, 66 | pick, 67 | watch, 68 | initialCache, 69 | immediate, 70 | ...fetchOptions 71 | } = opts 72 | 73 | const _fetchOptions = reactive({ 74 | ...fetchOptions, 75 | cache: typeof opts.cache === 'boolean' ? undefined : opts.cache 76 | }) 77 | 78 | const _asyncDataOptions: AsyncDataOptions<_ResT, Transform, PickKeys> = { 79 | server, 80 | lazy, 81 | default: defaultFn, 82 | transform, 83 | pick, 84 | initialCache, 85 | immediate, 86 | watch: [ 87 | _fetchOptions, 88 | _request, 89 | ...(watch || []) 90 | ] 91 | } 92 | 93 | const { $http } = useNuxtApp() 94 | 95 | const asyncData = useAsyncData<_ResT, ErrorT, Transform, PickKeys>(key, () => { 96 | //@ts-ignore: Ref does not contain toLowerCase() but will be converted 97 | const method = opts && opts.method && ['get', 'head', 'delete', 'post', 'put', 'patch'].includes(opts.method.toLowerCase()) ? opts.method.toLowerCase() : 'get' 98 | return $http['$' + method](_request.value, _fetchOptions) as Promise<_ResT> 99 | }, _asyncDataOptions) 100 | 101 | return asyncData 102 | } 103 | 104 | export function useLazyHttp< 105 | ResT = void, 106 | ErrorT = Error, 107 | ReqT extends NitroFetchRequest = NitroFetchRequest, 108 | _ResT = ResT extends void ? FetchResult : ResT, 109 | Transform extends (res: _ResT) => any = (res: _ResT) => _ResT, 110 | PickKeys extends KeyOfRes = KeyOfRes 111 | >( 112 | request: Ref | ReqT | (() => ReqT), 113 | opts?: Omit, 'lazy'> 114 | ): AsyncData, PickKeys>, ErrorT | null | true> 115 | export function useLazyHttp< 116 | ResT = void, 117 | ErrorT = Error, 118 | ReqT extends NitroFetchRequest = NitroFetchRequest, 119 | _ResT = ResT extends void ? FetchResult : ResT, 120 | Transform extends (res: _ResT) => any = (res: _ResT) => _ResT, 121 | PickKeys extends KeyOfRes = KeyOfRes 122 | >( 123 | request: Ref | ReqT | (() => ReqT), 124 | opts: Omit, 'lazy'> = {}, 125 | ) { 126 | 127 | return useHttp(request, { 128 | ...opts, 129 | lazy: true 130 | }) 131 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/http/src/module.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleOptions } from './types' 2 | import type { Nuxt } from '@nuxt/schema' 3 | import { addTemplate, defineNuxtModule, addPluginTemplate, createResolver, addImports } from '@nuxt/kit' 4 | import { name, version } from '../package.json' 5 | import { withHttps } from 'ufo' 6 | import { defu } from 'defu' 7 | 8 | const CONFIG_KEY = 'http' 9 | 10 | export default defineNuxtModule({ 11 | meta: { 12 | name, 13 | version, 14 | configKey: CONFIG_KEY, 15 | compatibility: { 16 | nuxt: '^3.0.0' 17 | }, 18 | }, 19 | defaults: {} as ModuleOptions, 20 | setup(opts: ModuleOptions, nuxt: Nuxt) { 21 | // Combine options with runtime config 22 | const moduleOptions: ModuleOptions = defu(nuxt.options.runtimeConfig?.public?.http, opts) 23 | 24 | // Default host 25 | const defaultHost = moduleOptions.host || process.env.NITRO_HOST || process.env.HOST || 'localhost' 26 | 27 | // Default port 28 | const defaultPort = moduleOptions.port || process.env.NITRO_PORT || process.env.PORT || 3000 29 | 30 | // Default prefix 31 | const prefix = moduleOptions.prefix || process.env.PREFIX || '/' 32 | 33 | // Apply defaults 34 | const options: ModuleOptions = defu(moduleOptions, { 35 | baseURL: `http://${defaultHost}:${defaultPort}${prefix}`, 36 | browserBaseURL: undefined, 37 | proxyHeaders: true, 38 | proxyHeadersIgnore: [ 39 | 'accept', 40 | 'connection', 41 | 'cf-connecting-ip', 42 | 'cf-ray', 43 | 'content-length', 44 | 'content-md5', 45 | 'content-type', 46 | 'host', 47 | 'if-modified-since', 48 | 'if-none-match', 49 | 'x-forwarded-host', 50 | 'x-forwarded-port', 51 | 'x-forwarded-proto' 52 | ], 53 | serverTimeout: 10000, 54 | clientTimeout: 25000, 55 | https: false, 56 | retry: 1, 57 | headers: { 58 | accept: 'application/json, text/plain, */*' 59 | }, 60 | credentials: 'omit', 61 | debug: false, 62 | interceptorPlugin: false 63 | }) 64 | 65 | if (typeof options.browserBaseURL === 'undefined') { 66 | options.browserBaseURL = nuxt.options.app.baseURL 67 | } 68 | 69 | // Convert http:// to https:// if https option is on 70 | if (options.https === true) { 71 | options.baseURL = withHttps(options.baseURL as string) 72 | options.browserBaseURL = withHttps(options.browserBaseURL) 73 | } 74 | 75 | // resolver 76 | const resolver = createResolver(import.meta.url) 77 | 78 | // Requires proxy module 79 | if (Object.hasOwn(nuxt.options, 'proxy') && moduleOptions.interceptorPlugin) { 80 | addPluginTemplate({ 81 | src: resolver.resolve('runtime/templates/interceptor.plugin.mjs'), 82 | filename: 'proxy.plugin.mjs', 83 | // @ts-ignore 84 | options: nuxt.options.proxy, 85 | }) 86 | } 87 | 88 | // Register plugin 89 | addPluginTemplate({ 90 | src: resolver.resolve('runtime/templates/http.plugin.mjs'), 91 | filename: 'http.plugin.mjs', 92 | options: options 93 | }) 94 | 95 | addTemplate({ 96 | getContents: () => getHttpDTS(), 97 | filename: 'http.plugin.d.ts', 98 | write: true, 99 | }) 100 | 101 | // Add auto imports 102 | const composables = resolver.resolve('runtime/composables') 103 | 104 | addImports([ 105 | { from: composables, name: 'useHttp' }, 106 | { from: composables, name: 'useLazyHttp' } 107 | ]) 108 | 109 | // Unsure if it works on windows still. 110 | if (process.platform !== "win32") { 111 | // create nitro plugin 112 | addTemplate({ 113 | getContents: () => nitroHttp(), 114 | filename: `nitro-http.mjs`, 115 | write: true 116 | }) 117 | 118 | nuxt.hook('nitro:config', (nitro) => { 119 | nitro.plugins = nitro.plugins || [] 120 | nitro.plugins.push(resolver.resolve(nuxt.options.buildDir, `nitro-http.mjs`)) 121 | }) 122 | } 123 | } 124 | }) 125 | 126 | function nitroHttp() { 127 | return `import { createInstance } from '@refactorjs/ofetch' 128 | 129 | export default function (nitroApp) { 130 | // should inherit defaults from $fetch 131 | globalThis.$http = createInstance({}, $fetch) 132 | } 133 | ` 134 | } 135 | 136 | function getHttpDTS() { 137 | return `import type { Plugin } from '#app' 138 | import { FetchInstance } from '@refactorjs/ofetch' 139 | 140 | declare const _default: Plugin<{ 141 | http: FetchInstance; 142 | }>; 143 | 144 | export default _default; 145 | ` 146 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/readme.md: -------------------------------------------------------------------------------- 1 | **Information** 2 | 3 | This module is meant as an alternative to @nuxtjs/auth, except this is for nuxt3 only with no backwards compatibility support. This will only work with pinia, I had originally had it work with vuex, but since that is in maintenece mode, I decided to switch to pinia. If you find any bugs please do tell me, I'm still working on this. 4 | 5 | **Typescript** 6 | 7 | Please note, any issues regarding typescript is not a priorty for me. If you're having issues with it it'll be noted but not a priority, I normally have typescript disabled to focus on the functionality of the module. 8 | 9 | **Refactored (Version 2.0.0+)** 10 | 11 | The module now requires '@nuxtjs-alt/http' to function in version 2.0.0 and up, that module extends ohmyfetch. Please note that if you were using `data` to post data, you now need to use `body` since this is what `ohmyfetch` uses. 12 | Please tell me if you encounter any issues with these changes. 13 | 14 | **Composable** 15 | 16 | A `useAuth()` composable is available to utitlize if `$auth` from `useNuxtApp()` isnt working out for you in terms of type hinting. 17 | 18 | **Options/Extra Features** 19 | 20 | Besides what nuxt auth normally offers, here are some other options/changes 21 | 22 | - `globalMiddleware`: `boolean` (Default: false) - Enables/disables the middleware to be used globally 23 | - `enableMiddleware`: `boolean` (Default: true) - Enables/disables the built-in middleware 24 | - `pinia.namespace`: `string` (Default: 'auth') - Changed from vuex to pinia, this is the namespace to use for the pinia store 25 | - `sessionStorage`: `string|false` (Default: 'auth.') - Similar to the localstorage option, there is a session storage options available for you to use. 26 | 27 | **Cookie-based auth** 28 | 29 | If you have any specific changes that need to be made to accomodate cookie based-auth please tell me, at this moment the way I configured it is that it pretty much does the same thing as the official auth module cookie, but in cases where the server autmaitcally attaches the server cookie to all requests it will function conrrently (in this case setting a cookie on all requests via laravel). 30 | 31 | the config would look like this 32 | 33 | ```ts 34 | auth: { 35 | strategies: { 36 | localStorage: false, 37 | cookie: { 38 | cookie: { 39 | server: true, // by default this is set based on if nuxt ssr is enabled 40 | name: 'token', 41 | }, 42 | endpoints: { 43 | csrf: false, 44 | login: { 45 | url: '/api/user/login', 46 | method: 'post' 47 | }, 48 | user: { 49 | url: '/api/user/me', 50 | method: 'get' 51 | } 52 | }, 53 | user: { 54 | property: { 55 | client: false, 56 | server: false 57 | }, 58 | autoFetch: true 59 | } 60 | }, 61 | } 62 | } 63 | ``` 64 | 65 | notice the `cookie.server` property, this indicates that the cookie we will be looking for will be set upon login otherwise we will be looking at a client/browser cookie. 66 | the cookie scheme has been moved to its own scheme so the user property takes place within the cookie strategy and doesnt extend the token scheme from the local scheme. There has also been 2 user properties one for the client/browser and one for the server. 67 | 68 | **Laravel Sanctum** 69 | 70 | Laravel Sanctum wokrs a tiny bit differently, It inherits the same config as the Cookie scheme (see above) here's what the config would look like: 71 | 72 | ```ts 73 | auth: { 74 | strategies: { 75 | laravelSanctum: { 76 | provider: 'laravel/sanctum', 77 | cookie: { 78 | server: true, // by default this is set based on if nuxt ssr is enabled 79 | name: 'XSRF-TOKEN', 80 | }, 81 | endpoints: { 82 | csrf: { 83 | url: '/sanctum/csrf-cookie' 84 | }, 85 | login: { 86 | url: '/login' 87 | }, 88 | logout: { 89 | url: '/logout' 90 | }, 91 | user: { 92 | url: '/api/user' 93 | } 94 | }, 95 | user: { 96 | property: { 97 | client: false, 98 | server: false 99 | }, 100 | autoFetch: true 101 | } 102 | }, 103 | } 104 | } 105 | ``` 106 | 107 | **Oauth2** 108 | 109 | Oauth2 now has client window authentication thanks to this pull request: https://github.com/nuxt-community/auth-module/pull/1746 110 | properties have been changed to: 111 | - `clientWindow`: `boolean` 112 | - `clientWindowWidth`: `number` 113 | - `clientWindowHeight`: `number` 114 | 115 | **Aliases** 116 | 117 | Available Aliases: 118 | - `#auth/runtime` 119 | - `#auth/utils` 120 | - `#auth/providers` 121 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/inc/configuration-document.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationDocumentRequestError } from './configuration-document-request-error'; 2 | import { OpenIDConnectScheme, OpenIDConnectSchemeEndpoints } from '../schemes'; 3 | import { OpenIDConnectConfigurationDocument } from '../../types'; 4 | import { Storage } from '../core/storage'; 5 | import { defu } from 'defu'; 6 | 7 | // eslint-disable-next-line no-console 8 | const ConfigurationDocumentWarning = (message: string) => 9 | console.warn(`[AUTH] [OPENID CONNECT] Invalid configuration. ${message}`); 10 | 11 | /** 12 | * A metadata document that contains most of the OpenID Provider's information, 13 | * such as the URLs to use and the location of the service's public signing keys. 14 | * You can find this document by appending the discovery document path 15 | * (/.well-known/openid-configuration) to the authority URL(https://example.com) 16 | * Eg. https://example.com/.well-known/openid-configuration 17 | * 18 | * More info: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig 19 | */ 20 | export class ConfigurationDocument { 21 | scheme: OpenIDConnectScheme; 22 | $storage: Storage; 23 | key: string; 24 | 25 | constructor(scheme: OpenIDConnectScheme, storage: Storage) { 26 | this.scheme = scheme; 27 | this.$storage = storage; 28 | this.key = '_configuration_document.' + this.scheme.name; 29 | } 30 | 31 | #set(value: OpenIDConnectConfigurationDocument | boolean) { 32 | return this.$storage.setState(this.key, value); 33 | } 34 | 35 | get(): OpenIDConnectConfigurationDocument { 36 | return this.$storage.getState(this.key); 37 | } 38 | 39 | set(value: OpenIDConnectConfigurationDocument | boolean) { 40 | this.#set(value); 41 | 42 | return value; 43 | } 44 | 45 | async request() { 46 | // Get Configuration document from state hydration 47 | // @ts-ignore 48 | const serverDoc: OpenIDConnectConfigurationDocument = this.scheme.$auth.ctx.payload!.data.$auth?.openIDConnect?.configurationDocument; 49 | 50 | if (process.client && serverDoc) { 51 | this.set(serverDoc); 52 | } 53 | 54 | if (!this.get()) { 55 | const configurationDocument = await this.scheme.requestHandler.http.$get(this.scheme.options.endpoints.configuration).catch((e: any) => Promise.reject(e)); 56 | 57 | // Push Configuration document to state hydration 58 | if (process.server) { 59 | this.scheme.$auth.ctx.payload!.data = { 60 | ...this.scheme.$auth.ctx.payload!.data, 61 | $auth: { 62 | /* use `openIDConnect` instead of `oidc` because it could not be picked up by `serverDoc` */ 63 | openIDConnect: { 64 | configurationDocument 65 | } 66 | } 67 | } 68 | } 69 | 70 | this.set(configurationDocument); 71 | } 72 | } 73 | 74 | validate() { 75 | const mapping = { 76 | responseType: 'response_types_supported', 77 | scope: 'scopes_supported', 78 | grantType: 'grant_types_supported', 79 | acrValues: 'acr_values_supported', 80 | }; 81 | 82 | Object.keys(mapping).forEach((optionsKey) => { 83 | const configDocument: OpenIDConnectConfigurationDocument = this.get(); 84 | const configDocumentKey = mapping[optionsKey as keyof typeof mapping]; 85 | const configDocumentValues = configDocument[configDocumentKey as keyof typeof configDocument]; 86 | const optionsValues = this.scheme.options[optionsKey as keyof typeof this.scheme.options]; 87 | 88 | if (typeof configDocumentValues !== 'undefined') { 89 | if (Array.isArray(optionsValues) && Array.isArray(configDocumentValues)) { 90 | optionsValues.forEach((optionsValue) => { 91 | if (!configDocumentValues.includes(optionsValue)) { 92 | ConfigurationDocumentWarning( 93 | `A value of scheme options ${optionsKey} is not supported by ${configDocumentKey} of by Authorization Server.` 94 | ); 95 | } 96 | }); 97 | } 98 | 99 | if (!Array.isArray(optionsValues) && Array.isArray(configDocumentValues) && !configDocumentValues.includes(optionsValues as string)) { 100 | ConfigurationDocumentWarning(`Value of scheme option ${optionsKey} is not supported by ${configDocumentKey} of by Authorization Server.`); 101 | } 102 | 103 | if (!Array.isArray(optionsValues) && !Array.isArray(configDocumentValues) && configDocumentValues !== optionsValues) { 104 | ConfigurationDocumentWarning(`Value of scheme option ${optionsKey} is not supported by ${configDocumentKey} of by Authorization Server.`); 105 | } 106 | } 107 | }); 108 | } 109 | 110 | async init() { 111 | await this.request().catch(() => { 112 | throw new ConfigurationDocumentRequestError(); 113 | }); 114 | this.validate(); 115 | this.setSchemeEndpoints(); 116 | } 117 | 118 | setSchemeEndpoints() { 119 | const configurationDocument = this.get(); 120 | 121 | this.scheme.options.endpoints = defu(this.scheme.options.endpoints, { 122 | authorization: configurationDocument.authorization_endpoint, 123 | token: configurationDocument.token_endpoint, 124 | userInfo: configurationDocument.userinfo_endpoint, 125 | logout: configurationDocument.end_session_endpoint, 126 | }) as OpenIDConnectSchemeEndpoints; 127 | } 128 | 129 | reset() { 130 | this.#set(false); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/schemes/cookie.ts: -------------------------------------------------------------------------------- 1 | import type { EndpointsOption, SchemePartialOptions, SchemeCheck, CookieUserOptions, HTTPRequest, HTTPResponse } from '../../types'; 2 | import type { Auth } from '..'; 3 | import { BaseScheme } from './base'; 4 | import { getProp } from '../../utils'; 5 | import { RequestHandler } from '../inc'; 6 | 7 | export interface CookieSchemeEndpoints extends EndpointsOption { 8 | login: HTTPRequest; 9 | logout: HTTPRequest | false; 10 | user: HTTPRequest | false; 11 | csrf: HTTPRequest | false; 12 | } 13 | 14 | export interface CookieSchemeCookie { 15 | name: string; 16 | server: boolean; 17 | } 18 | 19 | export interface CookieSchemeOptions { 20 | name: string; 21 | endpoints: CookieSchemeEndpoints; 22 | user: CookieUserOptions; 23 | cookie: CookieSchemeCookie; 24 | } 25 | 26 | const DEFAULTS: SchemePartialOptions = { 27 | name: 'cookie', 28 | cookie: { 29 | name: undefined, 30 | server: false, 31 | }, 32 | endpoints: { 33 | csrf: false, 34 | login: { 35 | url: '/api/auth/login', 36 | method: 'post', 37 | }, 38 | logout: { 39 | url: '/api/auth/logout', 40 | method: 'post', 41 | }, 42 | user: { 43 | url: '/api/auth/user', 44 | method: 'get', 45 | }, 46 | }, 47 | user: { 48 | property: { 49 | client: false, 50 | server: false, 51 | }, 52 | autoFetch: true, 53 | }, 54 | }; 55 | 56 | export class CookieScheme extends BaseScheme { 57 | requestHandler: RequestHandler; 58 | 59 | constructor($auth: Auth, options: SchemePartialOptions, ...defaults: SchemePartialOptions[]) { 60 | super($auth, options as OptionsT, ...(defaults as OptionsT[]), DEFAULTS as OptionsT); 61 | 62 | // Initialize Request Interceptor 63 | this.requestHandler = new RequestHandler(this, this.$auth.ctx.$http); 64 | } 65 | 66 | async mounted(): Promise { 67 | if (process.server) { 68 | this.$auth.ctx.$http.setHeader('referer', this.$auth.ctx.ssrContext?.event.req.headers.host); 69 | } 70 | 71 | // Initialize request interceptor 72 | this.initializeRequestInterceptor(); 73 | 74 | if (this.isServerCookie() || this.isClientCookie()) { 75 | // Fetch user once 76 | return this.$auth.fetchUserOnce(); 77 | } 78 | } 79 | 80 | check(): SchemeCheck { 81 | const response = { valid: false }; 82 | 83 | if (this.options.cookie.name) { 84 | const cookies = this.$auth.$storage.getCookies(); 85 | 86 | if (this.isServerCookie() || this.isClientCookie()) { 87 | response.valid = Boolean(cookies![this.options.cookie.name]); 88 | } else { 89 | response.valid = true; 90 | } 91 | 92 | return response; 93 | } 94 | 95 | response.valid = true; 96 | return response; 97 | } 98 | 99 | async login(endpoint: HTTPRequest): Promise { 100 | // Ditch any leftover local tokens before attempting to log in 101 | this.$auth.reset(); 102 | 103 | // Make CSRF request if required 104 | if (this.options.endpoints.csrf) { 105 | await this.$auth.request(this.options.endpoints.csrf); 106 | } 107 | 108 | if (!this.options.endpoints.login) { 109 | return; 110 | } 111 | 112 | // Make login request 113 | const response = await this.$auth.request(endpoint, this.options.endpoints.login); 114 | 115 | // Initialize request interceptor if not initialized 116 | if (!this.requestHandler.interceptor) { 117 | this.initializeRequestInterceptor(); 118 | } 119 | 120 | // Fetch user if `autoFetch` is enabled 121 | if (this.options.user.autoFetch) { 122 | await this.fetchUser(); 123 | } 124 | 125 | return response; 126 | } 127 | 128 | async fetchUser(endpoint?: HTTPRequest): Promise { 129 | if (this.isServerCookie() || this.isClientCookie()) { 130 | if (!this.check().valid) { 131 | return Promise.resolve(); 132 | } 133 | } 134 | 135 | // User endpoint is disabled 136 | if (!this.options.endpoints.user) { 137 | this.$auth.setUser({}); 138 | return Promise.resolve(); 139 | } 140 | 141 | // Try to fetch user and then set 142 | return this.$auth 143 | .requestWith(endpoint, this.options.endpoints.user) 144 | .then((response) => { 145 | let userData: any; 146 | 147 | if (process.client) { 148 | userData = getProp(response, this.options.user.property.client); 149 | } 150 | else { 151 | userData = getProp(response, this.options.user.property.server); 152 | } 153 | 154 | if (!userData) { 155 | const error = new Error(`User Data response does not contain field ${this.options.user.property}`); 156 | 157 | return Promise.reject(error); 158 | } 159 | 160 | this.$auth.setUser(userData); 161 | 162 | return response; 163 | }) 164 | .catch((error) => { 165 | this.$auth.callOnError(error, { method: 'fetchUser' }); 166 | return Promise.reject(error); 167 | }); 168 | } 169 | 170 | async logout(endpoint: HTTPRequest = {}): Promise { 171 | // Only connect to logout endpoint if it's configured 172 | if (this.options.endpoints.logout) { 173 | await this.$auth.requestWith(endpoint, this.options.endpoints.logout).catch((err: any) => console.error(err)); 174 | } 175 | 176 | // But reset regardless 177 | this.$auth.redirect('logout'); 178 | return this.$auth.reset(); 179 | } 180 | 181 | reset({ resetInterceptor = true } = {}): void { 182 | if (this.options.cookie.name) { 183 | this.$auth.$storage.setCookie(this.options.cookie.name, null, { 184 | prefix: '', 185 | }); 186 | } 187 | 188 | this.$auth.setUser(false); 189 | 190 | if (resetInterceptor) { 191 | this.requestHandler.reset(); 192 | } 193 | } 194 | 195 | isServerCookie(): boolean { 196 | return this.options.cookie.server && process.server; 197 | } 198 | 199 | isClientCookie(): boolean { 200 | return !this.options.cookie.server && process.client; 201 | } 202 | 203 | initializeRequestInterceptor(): void { 204 | this.requestHandler.initializeRequestInterceptor(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/schemes/refresh.ts: -------------------------------------------------------------------------------- 1 | import type { HTTPRequest, HTTPResponse, RefreshableScheme, RefreshableSchemeOptions, SchemeCheck, SchemePartialOptions } from '../../types'; 2 | import type { Auth } from '..'; 3 | import { cleanObj, getProp } from '../../utils'; 4 | import { RefreshController, RefreshToken, ExpiredAuthSessionError } from '../inc'; 5 | import { LocalScheme, LocalSchemeEndpoints, LocalSchemeOptions } from './local'; 6 | 7 | export interface RefreshSchemeEndpoints extends LocalSchemeEndpoints { 8 | refresh: HTTPRequest; 9 | } 10 | 11 | export interface RefreshSchemeOptions extends LocalSchemeOptions, RefreshableSchemeOptions { 12 | endpoints: RefreshSchemeEndpoints; 13 | autoLogout: boolean; 14 | } 15 | 16 | const DEFAULTS: SchemePartialOptions = { 17 | name: 'refresh', 18 | endpoints: { 19 | refresh: { 20 | url: '/api/auth/refresh', 21 | method: 'post', 22 | }, 23 | }, 24 | refreshToken: { 25 | property: 'refresh_token', 26 | data: 'refresh_token', 27 | maxAge: 60 * 60 * 24 * 30, 28 | required: true, 29 | tokenRequired: false, 30 | prefix: '_refresh_token.', 31 | expirationPrefix: '_refresh_token_expiration.', 32 | }, 33 | autoLogout: false, 34 | }; 35 | 36 | export class RefreshScheme extends LocalScheme implements RefreshableScheme 37 | { 38 | refreshToken: RefreshToken; 39 | refreshController: RefreshController; 40 | 41 | constructor($auth: Auth, options: SchemePartialOptions) { 42 | super($auth, options, DEFAULTS); 43 | 44 | // Initialize Refresh Token instance 45 | this.refreshToken = new RefreshToken(this, this.$auth.$storage); 46 | 47 | // Initialize Refresh Controller 48 | this.refreshController = new RefreshController(this); 49 | } 50 | 51 | check(checkStatus = false): SchemeCheck { 52 | const response = { 53 | valid: false, 54 | tokenExpired: false, 55 | refreshTokenExpired: false, 56 | isRefreshable: true, 57 | }; 58 | 59 | // Sync tokens 60 | const token = this.token.sync(); 61 | const refreshToken = this.refreshToken.sync(); 62 | 63 | // Token and refresh token are required but not available 64 | if (!token || !refreshToken) { 65 | return response; 66 | } 67 | 68 | // Check status wasn't enabled, let it pass 69 | if (!checkStatus) { 70 | response.valid = true; 71 | return response; 72 | } 73 | 74 | // Get status 75 | const tokenStatus = this.token.status(); 76 | const refreshTokenStatus = this.refreshToken.status(); 77 | 78 | // Refresh token has expired. There is no way to refresh. Force reset. 79 | if (refreshTokenStatus.expired()) { 80 | response.refreshTokenExpired = true; 81 | return response; 82 | } 83 | 84 | // Token has expired, Force reset. 85 | if (tokenStatus.expired()) { 86 | response.tokenExpired = true; 87 | return response; 88 | } 89 | 90 | response.valid = true; 91 | return response; 92 | } 93 | 94 | mounted(): Promise { 95 | return super.mounted({ 96 | tokenCallback: () => { 97 | if (this.options.autoLogout) { 98 | this.$auth.reset(); 99 | } 100 | }, 101 | // @ts-ignore 102 | refreshTokenCallback: () => { 103 | this.$auth.reset(); 104 | }, 105 | }); 106 | } 107 | 108 | async refreshTokens(): Promise { 109 | // Refresh endpoint is disabled 110 | if (!this.options.endpoints.refresh) { 111 | return Promise.resolve(); 112 | } 113 | 114 | // Token and refresh token are required but not available 115 | if (!this.check().valid) { 116 | return Promise.resolve(); 117 | } 118 | 119 | // Get refresh token status 120 | const refreshTokenStatus = this.refreshToken.status(); 121 | 122 | // Refresh token is expired. There is no way to refresh. Force reset. 123 | if (refreshTokenStatus.expired()) { 124 | this.$auth.reset(); 125 | 126 | throw new ExpiredAuthSessionError(); 127 | } 128 | 129 | // Delete current token from the request header before refreshing, if `tokenRequired` is disabled 130 | if (!this.options.refreshToken.tokenRequired) { 131 | this.requestHandler.clearHeader(); 132 | } 133 | 134 | const endpoint: { 135 | body: { 136 | [key: string]: string | boolean | undefined 137 | client_id: string | undefined, 138 | grant_type: string | undefined, 139 | }, 140 | } = { 141 | body: { 142 | client_id: undefined, 143 | grant_type: undefined, 144 | }, 145 | }; 146 | 147 | // Add refresh token to payload if required 148 | if (this.options.refreshToken.required && this.options.refreshToken.data) { 149 | endpoint.body[this.options.refreshToken.data] = this.refreshToken.get(); 150 | } 151 | 152 | // Add client id to payload if defined 153 | if (this.options.clientId) { 154 | endpoint.body.client_id = this.options.clientId; 155 | } 156 | 157 | // Add grant type to payload if defined 158 | if (this.options.grantType) { 159 | endpoint.body.grant_type = 'refresh_token'; 160 | } 161 | 162 | cleanObj(endpoint.body); 163 | 164 | // Make refresh request 165 | try { 166 | const response = await this.$auth.request(endpoint, this.options.endpoints.refresh); 167 | // Update tokens 168 | this.updateTokens(response, { isRefreshing: true }); 169 | return await response; 170 | } catch (error: any) { 171 | this.$auth.callOnError(error, { method: 'refreshToken' }); 172 | return await Promise.reject(error); 173 | } 174 | } 175 | 176 | setUserToken(token: string | boolean, refreshToken?: string | boolean): Promise { 177 | this.token.set(token); 178 | 179 | if (refreshToken) { 180 | this.refreshToken.set(refreshToken); 181 | } 182 | 183 | // Fetch user 184 | return this.fetchUser(); 185 | } 186 | 187 | reset({ resetInterceptor = true } = {}): void { 188 | this.$auth.setUser(false); 189 | this.token.reset(); 190 | this.refreshToken.reset(); 191 | 192 | if (resetInterceptor) { 193 | this.requestHandler.reset(); 194 | } 195 | } 196 | 197 | protected updateTokens(response: HTTPResponse, { isRefreshing = false, updateOnRefresh = true } = {}): void { 198 | const token = this.options.token?.required ? (getProp(response, this.options.token.property) as string) : true; 199 | const refreshToken = this.options.refreshToken.required ? (getProp(response, this.options.refreshToken.property) as string) : true; 200 | 201 | this.token.set(token); 202 | 203 | // Update refresh token if defined and if `isRefreshing` is `false` 204 | // If `isRefreshing` is `true`, then only update if `updateOnRefresh` is also `true` 205 | if (refreshToken && (!isRefreshing || (isRefreshing && updateOnRefresh))) { 206 | this.refreshToken.set(refreshToken); 207 | } 208 | } 209 | 210 | protected initializeRequestInterceptor(): void { 211 | this.requestHandler.initializeRequestInterceptor( 212 | this.options.endpoints.refresh.url 213 | ); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/schemes/local.ts: -------------------------------------------------------------------------------- 1 | import type { EndpointsOption, SchemePartialOptions, TokenableSchemeOptions, TokenableScheme, UserOptions, HTTPRequest, HTTPResponse, SchemeCheck } from '../../types'; 2 | import type { Auth } from '..'; 3 | import { getProp } from '../../utils'; 4 | import { Token, RequestHandler } from '../inc'; 5 | import { BaseScheme } from './base'; 6 | 7 | export interface LocalSchemeEndpoints extends EndpointsOption { 8 | login: HTTPRequest; 9 | logout: HTTPRequest | false; 10 | user: HTTPRequest | false; 11 | } 12 | 13 | export interface LocalSchemeOptions extends TokenableSchemeOptions { 14 | endpoints: LocalSchemeEndpoints; 15 | user: UserOptions; 16 | clientId: string | false; 17 | grantType: string | false; 18 | scope: string[] | false; 19 | } 20 | 21 | const DEFAULTS: SchemePartialOptions = { 22 | name: 'local', 23 | endpoints: { 24 | login: { 25 | url: '/api/auth/login', 26 | method: 'post', 27 | }, 28 | logout: { 29 | url: '/api/auth/logout', 30 | method: 'post', 31 | }, 32 | user: { 33 | url: '/api/auth/user', 34 | method: 'get', 35 | }, 36 | }, 37 | token: { 38 | property: 'token', 39 | type: 'Bearer', 40 | name: 'Authorization', 41 | maxAge: 1800, 42 | global: true, 43 | required: true, 44 | prefix: '_token.', 45 | expirationPrefix: '_token_expiration.', 46 | }, 47 | user: { 48 | property: 'user', 49 | autoFetch: true, 50 | }, 51 | clientId: false, 52 | grantType: false, 53 | scope: false, 54 | }; 55 | 56 | export class LocalScheme extends BaseScheme implements TokenableScheme 57 | { 58 | token: Token; 59 | requestHandler: RequestHandler; 60 | 61 | constructor($auth: Auth, options: SchemePartialOptions, ...defaults: SchemePartialOptions[]) { 62 | super($auth, options as OptionsT, ...(defaults as OptionsT[]), DEFAULTS as OptionsT); 63 | 64 | // Initialize Token instance 65 | this.token = new Token(this, this.$auth.$storage); 66 | 67 | // Initialize Request Interceptor 68 | this.requestHandler = new RequestHandler(this, this.$auth.ctx.$http); 69 | } 70 | 71 | check(checkStatus = false): SchemeCheck { 72 | const response = { 73 | valid: false, 74 | tokenExpired: false, 75 | }; 76 | 77 | // Sync token 78 | const token = this.token.sync(); 79 | 80 | // Token is required but not available 81 | if (!token) { 82 | return response; 83 | } 84 | 85 | // Check status wasn't enabled, let it pass 86 | if (!checkStatus) { 87 | response.valid = true; 88 | return response; 89 | } 90 | 91 | // Get status 92 | const tokenStatus = this.token.status(); 93 | 94 | // Token has expired. Attempt `tokenCallback` 95 | if (tokenStatus.expired()) { 96 | response.tokenExpired = true; 97 | return response; 98 | } 99 | 100 | response.valid = true; 101 | return response; 102 | } 103 | 104 | mounted({ tokenCallback = () => this.$auth.reset(), refreshTokenCallback = undefined } = {}): Promise { 105 | const { tokenExpired, refreshTokenExpired } = this.check(true); 106 | 107 | if (refreshTokenExpired && typeof refreshTokenCallback === 'function') { 108 | //@ts-ignore 109 | refreshTokenCallback(); 110 | } else if (tokenExpired && typeof tokenCallback === 'function') { 111 | tokenCallback(); 112 | } 113 | 114 | // Initialize request interceptor 115 | this.initializeRequestInterceptor(); 116 | 117 | // Fetch user once 118 | return this.$auth.fetchUserOnce(); 119 | } 120 | 121 | async login(endpoint: HTTPRequest, { reset = true } = {}): Promise { 122 | if (!this.options.endpoints.login) { 123 | return; 124 | } 125 | 126 | // Ditch any leftover local tokens before attempting to log in 127 | if (reset) { 128 | this.$auth.reset({ resetInterceptor: false }); 129 | } 130 | 131 | // Add client id to payload if defined 132 | if (this.options.clientId) { 133 | endpoint.body!['client_id' as keyof BodyInit] = this.options.clientId as keyof BodyInit; 134 | } 135 | 136 | // Add grant type to payload if defined 137 | if (this.options.grantType) { 138 | endpoint.body!['grant_type' as keyof BodyInit] = this.options.grantType as keyof BodyInit; 139 | } 140 | 141 | // Add scope to payload if defined 142 | if (this.options.scope) { 143 | endpoint.body!['scope' as keyof BodyInit] = this.options.scope as keyof BodyInit; 144 | } 145 | 146 | // Make login request 147 | const response = await this.$auth.request(endpoint, this.options.endpoints.login); 148 | 149 | // Update tokens 150 | this.updateTokens(response); 151 | 152 | // Initialize request interceptor if not initialized 153 | if (!this.requestHandler.interceptor) { 154 | this.initializeRequestInterceptor(); 155 | } 156 | 157 | // Fetch user if `autoFetch` is enabled 158 | if (this.options.user.autoFetch) { 159 | await this.fetchUser(); 160 | } 161 | 162 | return response; 163 | } 164 | 165 | setUserToken(token: string): Promise { 166 | this.token.set(token); 167 | 168 | // Fetch user 169 | return this.fetchUser(); 170 | } 171 | 172 | async fetchUser(endpoint?: HTTPRequest): Promise { 173 | // Token is required but not available 174 | if (!this.check().valid) { 175 | return Promise.resolve(); 176 | } 177 | 178 | // User endpoint is disabled 179 | if (!this.options.endpoints.user) { 180 | this.$auth.setUser({}); 181 | return Promise.resolve(); 182 | } 183 | 184 | // Try to fetch user and then set 185 | return this.$auth 186 | .requestWith(endpoint, this.options.endpoints.user) 187 | .then((response) => { 188 | const userData = getProp(response, this.options.user.property!); 189 | 190 | if (!userData) { 191 | const error = new Error(`User Data response does not contain field ${this.options.user.property}`); 192 | return Promise.reject(error); 193 | } 194 | 195 | this.$auth.setUser(userData); 196 | return response; 197 | }) 198 | .catch((error) => { 199 | this.$auth.callOnError(error, { method: 'fetchUser' }); 200 | return Promise.reject(error); 201 | }); 202 | } 203 | 204 | async logout(endpoint: HTTPRequest = {}): Promise { 205 | // Only connect to logout endpoint if it's configured 206 | if (this.options.endpoints.logout) { 207 | await this.$auth.requestWith(endpoint, this.options.endpoints.logout).catch((err: any) => console.error(err)); 208 | } 209 | 210 | // But reset regardless 211 | this.$auth.redirect('logout'); 212 | return this.$auth.reset(); 213 | } 214 | 215 | reset({ resetInterceptor = true } = {}): void { 216 | this.$auth.setUser(false); 217 | this.token.reset(); 218 | 219 | if (resetInterceptor) { 220 | this.requestHandler.reset(); 221 | } 222 | } 223 | 224 | protected updateTokens(response: HTTPResponse): void { 225 | const token = this.options.token?.required ? (getProp(response, this.options.token.property) as string) : true; 226 | 227 | this.token.set(token); 228 | } 229 | 230 | protected initializeRequestInterceptor(): void { 231 | this.requestHandler.initializeRequestInterceptor(); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /@nuxtjs-alt/axios/src/runtime/templates/axios.plugin.mjs: -------------------------------------------------------------------------------- 1 | import { defineNuxtPlugin } from '#imports' 2 | import Axios from 'axios' 3 | '<% if (options.retry) { %>' 4 | import axiosRetry from 'axios-retry' 5 | '<% } %>' 6 | 7 | // Axios.prototype cannot be modified 8 | const axiosExtra = { 9 | setBaseURL(baseURL) { 10 | this.defaults.baseURL = baseURL 11 | }, 12 | setHeader(name, value, scopes = 'common') { 13 | for (const scope of Array.isArray(scopes) ? scopes : [scopes]) { 14 | if (!value) { 15 | delete this.defaults.headers[scope][name]; 16 | continue 17 | } 18 | this.defaults.headers[scope][name] = value 19 | } 20 | }, 21 | setToken(token, type, scopes = 'common') { 22 | const value = !token ? null : (type ? type + ' ' : '') + token 23 | this.setHeader('Authorization', value, scopes) 24 | }, 25 | onRequest(fn) { 26 | this.interceptors.request.use(config => fn(config) || config) 27 | }, 28 | onResponse(fn) { 29 | this.interceptors.response.use(response => fn(response) || response) 30 | }, 31 | onRequestError(fn) { 32 | this.interceptors.request.use(undefined, error => fn(error) || Promise.reject(error)) 33 | }, 34 | onResponseError(fn) { 35 | this.interceptors.response.use(undefined, error => fn(error) || Promise.reject(error)) 36 | }, 37 | onError(fn) { 38 | this.onRequestError(fn) 39 | this.onResponseError(fn) 40 | }, 41 | create(options) { 42 | return createAxiosInstance({ ...this.defaults, ...options }) 43 | } 44 | } 45 | 46 | // Request helpers ($get, $post, ...) 47 | for (const method of ['request', 'delete', 'get', 'head', 'options', 'post', 'put', 'patch']) { 48 | axiosExtra['$' + method] = function () { return this[method].apply(this, arguments).then(res => res && res.data) } 49 | } 50 | 51 | const extendAxiosInstance = axios => { 52 | for (const key in axiosExtra) { 53 | axios[key] = axiosExtra[key].bind(axios) 54 | } 55 | } 56 | 57 | const createAxiosInstance = axiosOptions => { 58 | // Create new axios instance 59 | const axios = Axios.create(axiosOptions) 60 | axios.CancelToken = Axios.CancelToken 61 | axios.isCancel = Axios.isCancel 62 | axios.isAxiosError = Axios.isAxiosError 63 | 64 | // Extend axios proto 65 | extendAxiosInstance(axios) 66 | 67 | // Intercept to apply default headers 68 | axios.onRequest((config) => { 69 | config.headers = { ...axios.defaults.headers.common, ...config.headers } 70 | }) 71 | 72 | // Setup interceptors 73 | '<% if (options.debug) { %>'; setupDebugInterceptor(axios); '<% } %>' 74 | '<% if (options.credentials) { %>'; setupCredentialsInterceptor(axios); '<% } %>' 75 | '<% if (options.progress) { %>'; setupProgress(axios); '<% } %>' 76 | '<% if (options.retry) { %>'; axiosRetry(axios, JSON.parse('<%= JSON.stringify(options.retry) %>')); '<% } %>' 77 | 78 | return axios 79 | } 80 | 81 | '<% if (options.debug) { %>' 82 | const log = (level, ...messages) => console[level]('[Axios]', ...messages) 83 | 84 | const setupDebugInterceptor = axios => { 85 | // request 86 | axios.onRequestError(error => { 87 | log('error', 'Request error:', error) 88 | }) 89 | 90 | // response 91 | axios.onResponseError(error => { 92 | log('error', 'Response error:', error) 93 | }) 94 | axios.onResponse(res => { 95 | log( 96 | 'info', 97 | '[' + (res.status + ' ' + res.statusText) + ']', 98 | '[' + res.config.method.toUpperCase() + ']', 99 | res.config.url) 100 | 101 | if (process.client) { 102 | console.log(res) 103 | } else { 104 | console.log(JSON.stringify(res.data, undefined, 2)) 105 | } 106 | 107 | return res 108 | }) 109 | } 110 | '<% } %>' 111 | 112 | '<% if (options.credentials) { %>' 113 | const setupCredentialsInterceptor = axios => { 114 | // Send credentials only to relative and API Backend requests 115 | axios.onRequest(config => { 116 | if (config.withCredentials === undefined) { 117 | if (!/^https?:\/\//i.test(config.url) || config.url.indexOf(config.baseURL) === 0) { 118 | config.withCredentials = true 119 | } 120 | } 121 | }) 122 | } 123 | '<% } %>' 124 | 125 | '<% if (options.progress) { %>' 126 | const setupProgress = (axios) => { 127 | if (process.server) { 128 | return 129 | } 130 | 131 | // A noop loading inteterface for when $nuxt is not yet ready 132 | const noopLoading = { 133 | finish: () => { }, 134 | start: () => { }, 135 | fail: () => { }, 136 | set: () => { } 137 | } 138 | 139 | const $loading = () => { 140 | const $nuxt = typeof window !== 'undefined' && window['$<%= options.globalName %>'] 141 | return ($nuxt && $nuxt.$loading && $nuxt.$loading.set) ? $nuxt.$loading : noopLoading 142 | } 143 | 144 | let currentRequests = 0 145 | 146 | axios.onRequest(config => { 147 | if (config && config.progress === false) { 148 | return 149 | } 150 | 151 | currentRequests++ 152 | }) 153 | 154 | axios.onResponse(response => { 155 | if (response && response.config && response.config.progress === false) { 156 | return 157 | } 158 | 159 | currentRequests-- 160 | if (currentRequests <= 0) { 161 | currentRequests = 0 162 | $loading().finish() 163 | } 164 | }) 165 | 166 | axios.onError(error => { 167 | if (error && error.config && error.config.progress === false) { 168 | return 169 | } 170 | 171 | currentRequests-- 172 | 173 | if (Axios.isCancel(error)) { 174 | if (currentRequests <= 0) { 175 | currentRequests = 0 176 | $loading().finish() 177 | } 178 | return 179 | } 180 | 181 | $loading().fail() 182 | $loading().finish() 183 | }) 184 | 185 | const onProgress = e => { 186 | if (!currentRequests || !e.total) { 187 | return 188 | } 189 | const progress = ((e.loaded * 100) / (e.total * currentRequests)) 190 | $loading().set(Math.min(100, progress)) 191 | } 192 | 193 | axios.defaults.onUploadProgress = onProgress 194 | axios.defaults.onDownloadProgress = onProgress 195 | } 196 | '<% } %>' 197 | 198 | export default defineNuxtPlugin(ctx => { 199 | // runtimeConfig 200 | const runtimeConfig = ctx.$config && ctx.$config.public.axios || {} 201 | 202 | // Nuxt Options 203 | const nuxtOptions = JSON.parse('<%= JSON.stringify(options) %>') 204 | 205 | // baseURL 206 | const baseURL = process.client ? (runtimeConfig.browserBaseURL || runtimeConfig.browserBaseUrl || runtimeConfig.baseURL || runtimeConfig.baseUrl || nuxtOptions.browserBaseURL || '') : (runtimeConfig.baseURL || runtimeConfig.baseUrl || process.env._AXIOS_BASE_URL_ || nuxtOptions.baseURL || '') 207 | 208 | const axiosOptions = { 209 | baseURL, 210 | headers: nuxtOptions.headers, 211 | } 212 | 213 | if (nuxtOptions.proxyHeaders) { 214 | // Proxy SSR request headers 215 | if (process.server && ctx.ssrContext?.event.req && ctx.ssrContext?.event.req.headers) { 216 | const reqHeaders = { ...ctx.ssrContext?.event.req.headers } 217 | for (const h of nuxtOptions.proxyHeadersIgnore) { 218 | delete reqHeaders[h] 219 | } 220 | 221 | axiosOptions.headers.common = { ...reqHeaders, ...axiosOptions.headers.common } 222 | } 223 | } 224 | 225 | if (process.server) { 226 | // Don't accept brotli encoding because Node can't parse it 227 | axiosOptions.headers.common['accept-encoding'] = 'gzip, deflate' 228 | } 229 | 230 | const axios = createAxiosInstance(axiosOptions) 231 | 232 | globalThis['$axios'] = axios 233 | ctx.provide('axios', axios); 234 | }) -------------------------------------------------------------------------------- /@nuxtjs-alt/proxy/src/module.ts: -------------------------------------------------------------------------------- 1 | import { addServerHandler, addTemplate, addPluginTemplate, createResolver, defineNuxtModule } from '@nuxt/kit' 2 | import { name, version } from '../package.json' 3 | import { ModuleOptions, ProxyOptions } from './types' 4 | import { defu } from 'defu' 5 | 6 | const CONFIG_KEY = 'proxy' 7 | 8 | export default defineNuxtModule({ 9 | meta: { 10 | name, 11 | version, 12 | configKey: CONFIG_KEY, 13 | compatibility: { 14 | nuxt: '^3.0.0' 15 | } 16 | }, 17 | defaults: { 18 | enableProxy: true, 19 | fetch: false 20 | }, 21 | setup(options, nuxt) { 22 | const config = (nuxt.options.runtimeConfig.proxy = defu(nuxt.options.runtimeConfig.proxy, options)) as ModuleOptions 23 | const resolver = createResolver(import.meta.url) 24 | const defaultHost = process.env.NITRO_HOST || process.env.HOST || 'localhost' 25 | const defaultPort = process.env.NITRO_PORT || process.env.PORT || 3000 26 | 27 | if (config.enableProxy) { 28 | // Create Proxy 29 | addTemplate({ 30 | filename: 'nuxt-proxy.ts', 31 | write: true, 32 | getContents: () => proxyMiddlewareContent(config.proxies ?? {}) 33 | }) 34 | 35 | addServerHandler({ 36 | handler: resolver.resolve(nuxt.options.buildDir, 'nuxt-proxy.ts'), 37 | middleware: true 38 | }) 39 | } 40 | 41 | if (config.fetch) { 42 | // Create Interceptor 43 | addPluginTemplate({ 44 | src: resolver.resolve('runtime/fetch.interceptor.mjs'), 45 | filename: 'fetch.interceptor.mjs', 46 | options: config 47 | }) 48 | 49 | nuxt.options.build.transpile.push(resolver.resolve('runtime')) 50 | } 51 | 52 | // Don't know if it runs on windows still. 53 | if (config.fetch && process.platform !== "win32") { 54 | // create nitro plugin 55 | addTemplate({ 56 | getContents: () => nitroFetchProxy(defaultHost, defaultPort), 57 | filename: 'nitro-fetch.mjs', 58 | write: true 59 | }) 60 | 61 | nuxt.hook('nitro:config', (nitro) => { 62 | nitro.plugins = nitro.plugins || [] 63 | nitro.plugins.push(resolver.resolve(nuxt.options.buildDir, 'nitro-fetch.mjs')) 64 | }) 65 | } 66 | } 67 | }) 68 | 69 | function nitroFetchProxy(host: string, port: number | string): string { 70 | return `import { createFetch, Headers } from 'ofetch' 71 | 72 | export default function (nitroApp) { 73 | // the proxy module needs the host and port of the nitro server in order for it to proxy it properly. 74 | // By default only a path is being submitted so this will chnage it to the host and port 75 | globalThis.$fetch = createFetch({ fetch: nitroApp.localFetch, Headers, defaults: { baseURL: 'http://${host}:${port}' } }) 76 | } 77 | ` 78 | } 79 | 80 | function converter(key: string, val: any) { 81 | if (typeof val === 'function' || val && val.constructor === RegExp) { 82 | return String(val) 83 | } 84 | return val 85 | } 86 | 87 | function proxyMiddlewareContent(options: ProxyOptions): string { 88 | return `import * as http from 'node:http' 89 | import * as net from 'node:net' 90 | import { createProxyServer, ProxyServer, Server } from '@refactorjs/http-proxy' 91 | import { defineEventHandler } from 'h3' 92 | 93 | interface ProxyOptions extends Server.ServerOptions { 94 | /** 95 | * rewrite path 96 | */ 97 | rewrite?: (path: string) => string | null | undefined | false 98 | 99 | /** 100 | * configure the proxy server (e.g. listen to events) 101 | */ 102 | configure?: (proxy: ProxyServer, options: ProxyOptions) => void | null | undefined | false 103 | 104 | /** 105 | * webpack-dev-server style bypass function 106 | */ 107 | bypass?: ( 108 | req: http.IncomingMessage, 109 | res: http.ServerResponse, 110 | options: ProxyOptions 111 | ) => void | null | undefined | false | string 112 | } 113 | 114 | // lazy require only when proxy is used 115 | const proxies: Record = {} 116 | const options: { [key: string]: string | ProxyOptions } | undefined = ${JSON.stringify(options, converter)}; 117 | 118 | Object.keys(options!).forEach((context) => { 119 | let opts = options![context] 120 | 121 | if (!opts) return 122 | 123 | if (typeof opts === 'string') { 124 | opts = { target: opts, changeOrigin: true } as ProxyOptions 125 | } 126 | 127 | if (isObject(opts)) { 128 | opts = { changeOrigin: true, ...opts } as ProxyOptions 129 | opts.rewrite = opts.rewrite ? new Function("return (" + opts.rewrite + ")")() : false 130 | opts.configure = opts.configure ? new Function("return (" + opts.configure + ")")() : false 131 | opts.bypass = opts.bypass ? new Function("return (" + opts.bypass + ")")() : false 132 | } 133 | 134 | const proxy = createProxyServer(opts) 135 | 136 | proxy.on('error', (err, req, originalRes) => { 137 | // When it is ws proxy, res is net.Socket 138 | const res = originalRes as http.ServerResponse | net.Socket 139 | if ('req' in res) { 140 | console.error('http proxy error:' + err.stack, { 141 | timestamp: true, 142 | error: err 143 | }) 144 | if (!res.headersSent && !res.writableEnded) { 145 | res.writeHead(500, { 146 | 'Content-Type': 'text/plain' 147 | }) 148 | .end() 149 | } 150 | } else { 151 | console.error('ws proxy error:' + err.stack, { 152 | timestamp: true, 153 | error: err 154 | }) 155 | res.end() 156 | } 157 | }) 158 | 159 | if (opts.configure) { 160 | opts.configure(proxy, opts) 161 | } 162 | 163 | // clone before saving because http-proxy mutates the options 164 | proxies[context] = [proxy, { ...opts }] 165 | }) 166 | 167 | export default defineEventHandler(async (event) => { 168 | await new Promise((resolve, reject) => { 169 | const next = (err?: unknown) => { 170 | if (err) { 171 | reject(err) 172 | } else { 173 | resolve() 174 | } 175 | } 176 | 177 | const url = event.req.url! 178 | 179 | for (const context in proxies) { 180 | if (doesProxyContextMatchUrl(context, url)) { 181 | const [proxy, opts] = proxies[context] 182 | const options: Server.ServerOptions = {} 183 | 184 | if (opts.bypass) { 185 | const bypassResult = opts.bypass(event.req, event.res, opts) 186 | if (typeof bypassResult === 'string') { 187 | event.req.url = bypassResult 188 | console.debug('bypass: ' + event.req.url + ' -> ' + bypassResult) 189 | return next() 190 | } else if (isObject(bypassResult)) { 191 | Object.assign(options, bypassResult) 192 | console.debug('bypass: ' + event.req.url + ' use modified options: %O', options) 193 | return next() 194 | } else if (bypassResult === false) { 195 | console.debug('bypass: ' + event.req.url + ' -> 404') 196 | return event.res.end(404) 197 | } 198 | } 199 | 200 | console.debug(event.req.url + ' -> ' + opts.target || opts.forward) 201 | 202 | if (opts.rewrite) { 203 | event.req.url = opts.rewrite(event.req.url!) as string 204 | } 205 | 206 | proxy.web(event.req, event.res, options) 207 | return 208 | } 209 | } 210 | next() 211 | }) 212 | }) 213 | 214 | function isObject(value: unknown): value is Record { 215 | return Object.prototype.toString.call(value) === '[object Object]' 216 | } 217 | 218 | function doesProxyContextMatchUrl(context: string, url: string): boolean { 219 | return ( 220 | (context.startsWith('^') && new RegExp(context).test(url)) || url.startsWith(context) 221 | ) 222 | } 223 | ` 224 | } -------------------------------------------------------------------------------- /@nuxtjs-alt/auth/src/runtime/schemes/openIDConnect.ts: -------------------------------------------------------------------------------- 1 | import type { HTTPResponse, SchemeCheck, SchemePartialOptions } from '../../types'; 2 | import type { Auth } from '..'; 3 | import { Oauth2Scheme, Oauth2SchemeEndpoints, Oauth2SchemeOptions } from './oauth2'; 4 | import { normalizePath, getProp } from '../../utils'; 5 | import { IdToken, ConfigurationDocument } from '../inc'; 6 | import { IdTokenableSchemeOptions } from '../../types'; 7 | import { useRoute } from '#imports'; 8 | import { getQuery, withQuery, QueryObject, QueryValue } from 'ufo' 9 | 10 | export interface OpenIDConnectSchemeEndpoints extends Oauth2SchemeEndpoints { 11 | configuration: string; 12 | } 13 | 14 | export interface OpenIDConnectSchemeOptions extends Oauth2SchemeOptions, IdTokenableSchemeOptions { 15 | endpoints: OpenIDConnectSchemeEndpoints; 16 | } 17 | 18 | const DEFAULTS: SchemePartialOptions = { 19 | name: 'openIDConnect', 20 | responseType: 'code', 21 | grantType: 'authorization_code', 22 | scope: ['openid', 'profile', 'offline_access'], 23 | idToken: { 24 | property: 'id_token', 25 | maxAge: 1800, 26 | prefix: '_id_token.', 27 | expirationPrefix: '_id_token_expiration.', 28 | }, 29 | codeChallengeMethod: 'S256', 30 | }; 31 | 32 | export class OpenIDConnectScheme extends Oauth2Scheme { 33 | idToken: IdToken; 34 | configurationDocument: ConfigurationDocument; 35 | 36 | constructor($auth: Auth, options: SchemePartialOptions, ...defaults: SchemePartialOptions[]) { 37 | super($auth, options as OptionsT, ...(defaults as OptionsT[]), DEFAULTS as OptionsT); 38 | 39 | // Initialize ID Token instance 40 | this.idToken = new IdToken(this, this.$auth.$storage); 41 | 42 | // Initialize ConfigurationDocument 43 | this.configurationDocument = new ConfigurationDocument(this, this.$auth.$storage); 44 | } 45 | 46 | protected updateTokens(response: HTTPResponse): void { 47 | super.updateTokens(response); 48 | const idToken = getProp(response, this.options.idToken.property) as string; 49 | 50 | if (idToken) { 51 | this.idToken.set(idToken); 52 | } 53 | } 54 | 55 | check(checkStatus = false): SchemeCheck { 56 | const response: SchemeCheck = { 57 | valid: false, 58 | tokenExpired: false, 59 | refreshTokenExpired: false, 60 | idTokenExpired: false, 61 | isRefreshable: true, 62 | }; 63 | 64 | // Sync tokens 65 | const token = this.token.sync(); 66 | this.refreshToken.sync(); 67 | this.idToken.sync(); 68 | 69 | // Token is required but not available 70 | if (!token) { 71 | return response; 72 | } 73 | 74 | // Check status wasn't enabled, let it pass 75 | if (!checkStatus) { 76 | response.valid = true; 77 | return response; 78 | } 79 | 80 | // Get status 81 | const tokenStatus = this.token.status(); 82 | const refreshTokenStatus = this.refreshToken.status(); 83 | const idTokenStatus = this.idToken.status(); 84 | 85 | // Refresh token has expired. There is no way to refresh. Force reset. 86 | if (refreshTokenStatus.expired()) { 87 | response.refreshTokenExpired = true; 88 | return response; 89 | } 90 | 91 | // Token has expired, Force reset. 92 | if (tokenStatus.expired()) { 93 | response.tokenExpired = true; 94 | return response; 95 | } 96 | 97 | // Id token has expired. Force reset. 98 | if (idTokenStatus.expired()) { 99 | response.idTokenExpired = true; 100 | return response; 101 | } 102 | 103 | response.valid = true; 104 | return response; 105 | } 106 | 107 | async mounted() { 108 | // Get and validate configuration based upon OpenIDConnect Configuration document 109 | // https://openid.net/specs/openid-connect-configuration-1_0.html 110 | await this.configurationDocument.init(); 111 | 112 | const { tokenExpired, refreshTokenExpired } = this.check(true); 113 | 114 | // Force reset if refresh token has expired 115 | // Or if `autoLogout` is enabled and token has expired 116 | if (refreshTokenExpired || (tokenExpired && this.options.autoLogout)) { 117 | this.$auth.reset(); 118 | } 119 | 120 | // Initialize request interceptor 121 | this.requestHandler.initializeRequestInterceptor(this.options.endpoints.token); 122 | 123 | // Handle callbacks on page load 124 | const redirected = await this.#handleCallback(); 125 | 126 | if (!redirected) { 127 | return this.$auth.fetchUserOnce(); 128 | } 129 | } 130 | 131 | reset() { 132 | this.$auth.setUser(false); 133 | this.token.reset(); 134 | this.idToken.reset(); 135 | this.refreshToken.reset(); 136 | this.requestHandler.reset(); 137 | this.configurationDocument.reset(); 138 | } 139 | 140 | logout() { 141 | if (this.options.endpoints.logout) { 142 | const opts: QueryObject = { 143 | id_token_hint: this.idToken.get() as QueryValue, 144 | post_logout_redirect_uri: this.logoutRedirectURI, 145 | }; 146 | const url = withQuery(this.options.endpoints.logout, opts); 147 | window.location.replace(url); 148 | } 149 | return this.$auth.reset(); 150 | } 151 | 152 | async fetchUser() { 153 | if (!this.check().valid) { 154 | return; 155 | } 156 | 157 | if (this.idToken.get()) { 158 | const data = this.idToken.userInfo(); 159 | this.$auth.setUser(data); 160 | return; 161 | } 162 | 163 | if (!this.options.endpoints.userInfo) { 164 | this.$auth.setUser({}); 165 | return; 166 | } 167 | 168 | const data = await this.$auth.requestWith({ 169 | url: this.options.endpoints.userInfo, 170 | }); 171 | 172 | this.$auth.setUser(data); 173 | } 174 | 175 | async #handleCallback() { 176 | const route = useRoute(); 177 | // Handle callback only for specified route 178 | if (this.$auth.options.redirect && normalizePath(route.path) !== normalizePath(this.$auth.options.redirect.callback)) { 179 | return; 180 | } 181 | 182 | // Callback flow is not supported in server side 183 | if (process.server) { 184 | return; 185 | } 186 | 187 | const hash = getQuery(route.hash.slice(1)); 188 | const parsedQuery = Object.assign({}, route.query, hash); 189 | 190 | // accessToken/idToken 191 | let token: string = parsedQuery[this.options.token!.property] as string; 192 | 193 | // refresh token 194 | let refreshToken: string; 195 | if (this.options.refreshToken.property) { 196 | refreshToken = parsedQuery[this.options.refreshToken.property] as string; 197 | } 198 | 199 | // id token 200 | let idToken = parsedQuery[this.options.idToken.property] as string; 201 | 202 | // Validate state 203 | const state = this.$auth.$storage.getUniversal(this.name + '.state'); 204 | this.$auth.$storage.setUniversal(this.name + '.state', null); 205 | if (state && parsedQuery.state !== state) { 206 | return; 207 | } 208 | 209 | // -- Authorization Code Grant -- 210 | if (this.options.responseType === 'code' && parsedQuery.code) { 211 | let codeVerifier: any; 212 | 213 | // Retrieve code verifier and remove it from storage 214 | if (this.options.codeChallengeMethod && this.options.codeChallengeMethod !== 'implicit') { 215 | codeVerifier = this.$auth.$storage.getUniversal(this.name + '.pkce_code_verifier'); 216 | this.$auth.$storage.setUniversal(this.name + '.pkce_code_verifier', null); 217 | } 218 | 219 | const response = await this.$auth.request({ 220 | method: 'post', 221 | url: this.options.endpoints.token, 222 | baseURL: '', 223 | headers: { 224 | 'Content-Type': 'application/x-www-form-urlencoded' 225 | }, 226 | body: new URLSearchParams({ 227 | code: parsedQuery.code as string, 228 | client_id: this.options.clientId, 229 | redirect_uri: this.redirectURI, 230 | response_type: this.options.responseType, 231 | audience: this.options.audience, 232 | grant_type: this.options.grantType, 233 | code_verifier: codeVerifier, 234 | }), 235 | }); 236 | 237 | token = (getProp(response, this.options.token!.property) as string) || token; 238 | refreshToken = (getProp(response, this.options.refreshToken.property!) as string) || refreshToken!; 239 | idToken = (getProp(response, this.options.idToken.property) as string) || idToken; 240 | } 241 | 242 | if (!token || !token.length) { 243 | return; 244 | } 245 | 246 | // Set token 247 | this.token.set(token); 248 | 249 | // Store refresh token 250 | if (refreshToken! && refreshToken.length) { 251 | this.refreshToken.set(refreshToken); 252 | } 253 | 254 | if (idToken && idToken.length) { 255 | this.idToken.set(idToken); 256 | } 257 | 258 | // Redirect to home 259 | this.$auth.redirect('home', false, false); 260 | 261 | return true; // True means a redirect happened 262 | } 263 | } 264 | --------------------------------------------------------------------------------