Skip to content

Commit 946768b

Browse files
authored
v3.0.3 (#83)
* feature & fix: response error interceptor * function edge-cases * feature: redirects as functions * fix: lint * update: readme * update: readme
1 parent 4f1f7dd commit 946768b

File tree

12 files changed

+113
-26
lines changed

12 files changed

+113
-26
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ export default defineNuxtConfig({
4040

4141
```
4242

43+
## Documentation
44+
I do not have the resources to host a dedicated documentation page, however I have been making updates to a [Stackblitz](https://stackblitz.com/edit/github-nufjhw-1bec1b) container so you may check there
45+
4346
## Changes
4447

4548
The module now uses '@nuxt-alt/http' to function, 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. If you intend to use ssr, please consider using the `@nuxt-alt/proxy` module.
@@ -138,6 +141,24 @@ The default cookie storage options.
138141

139142
The type of redirection strategy you want to use, `storage` utilizng localStorage for redirects, `query` utilizing the route query parameters.
140143

144+
### `resetOnResponseError`
145+
146+
- Type: `Boolean | Function`
147+
- Default: `false`
148+
149+
When enabled it will reset when there's a 401 error in any of the responses. You are able to turn this into a function to handle this yourself:
150+
```ts
151+
auth: {
152+
//... module options
153+
resetOnResponseError: (error, auth, scheme) => {
154+
if (error.response.status === 401) {
155+
scheme.reset?.()
156+
auth.redirect('login')
157+
}
158+
},
159+
}
160+
```
161+
141162
## TypeScript (2.6.0+)
142163
The user information can be edited like so for TypeScript:
143164
```ts

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nuxt-alt/auth",
3-
"version": "3.0.2",
3+
"version": "3.0.3",
44
"description": "An alternative module to @nuxtjs/auth",
55
"homepage": "https://github.com/nuxt-alt/auth",
66
"author": "Denoder",

src/options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const moduleDefaults: ModuleOptions = {
1010

1111
resetOnError: false,
1212

13+
resetOnResponseError: false,
14+
1315
ignoreExceptions: false,
1416

1517
// -- Authorization --

src/plugin.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,41 @@ import { defu } from 'defu';
2727
${options.schemeImports.map((i) => `import { ${i.name}${i.name !== i.as ? ' as ' + i.as : ''} } from '${i.from}'`).join('\n')}
2828
2929
// Options
30-
const options = ${JSON.stringify(options.options, null, 4)}
30+
let options = ${JSON.stringify(options.options, converter, 4)}
31+
32+
function parse(config) {
33+
const functionRegex1 = "/\\b\\w+\\s*\\((.*?)\\)\\s*\\{|function\\s*\\((.*?)\\)\\s*\\{|(\\w+)\\s*=>|\\([^)]*\\)\\s*=>|\\basync\\s+function\\b/";
34+
const functionRegex2 = "/\\b\\w+(\\s)+\\((.*?)\\)\\s*\\{/";
35+
36+
for (let prop in config) {
37+
if (typeof config[prop] === 'string' && (new RegExp(functionRegex1).test(config[prop]) || new RegExp(functionRegex2).test(config[prop]))) {
38+
const paramsStart = config[prop].indexOf('(');
39+
const paramsEnd = config[prop].indexOf(')');
40+
const functionParams = config[prop].substring(paramsStart + 1, paramsEnd).split(',').map(item => item.trim());
41+
42+
let functionBody;
43+
if (config[prop].includes('{')) {
44+
const functionBodyStart = config[prop].indexOf('{');
45+
const functionBodyEnd = config[prop].lastIndexOf('}');
46+
functionBody = config[prop].substring(functionBodyStart + 1, functionBodyEnd);
47+
config[prop] = new Function('return function(' + functionParams.join(',') + '){' + functionBody + '}')();
48+
} else {
49+
functionBody = config[prop].substring(paramsEnd + 1).trim();
50+
if (functionBody.startsWith('=>')) functionBody = functionBody.slice(2).trim();
51+
config[prop] = new Function('return function(' + functionParams.join(',') + '){return ' + functionBody + '}')();
52+
}
53+
} else if (typeof config[prop] === 'object' && config[prop] !== null) {
54+
parse(config[prop]);
55+
}
56+
}
57+
58+
return config
59+
}
3160
3261
export default defineNuxtPlugin({
3362
name: 'nuxt-alt:auth',
3463
async setup(nuxtApp) {
64+
options = parse(options)
3565
// Create a new Auth instance
3666
const auth = new Auth(nuxtApp, options)
3767
@@ -60,3 +90,11 @@ export default defineNuxtPlugin({
6090
}
6191
})`
6292
}
93+
94+
function converter(key: string, val: any) {
95+
if (val && val.constructor === RegExp || typeof val === 'function') {
96+
return String(val)
97+
}
98+
99+
return val
100+
}

src/runtime/core/auth.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@ export class Auth {
2727
this.ctx.hook('i18n:localeSwitched', () => {
2828
this.#transformRedirect(this.options.redirect);
2929
})
30-
31-
// Apply to initial options
32-
this.#transformRedirect(options.redirect);
3330
}
3431

32+
// Apply to initial options
33+
this.#transformRedirect(options.redirect);
3534
this.options = options;
3635

3736
// Storage & State
@@ -57,7 +56,13 @@ export class Auth {
5756
if (typeof value === 'string' && typeof this.ctx.$localePath === 'function') {
5857
redirects[key as keyof typeof this.options.redirect] = this.ctx.$localePath(value);
5958
}
59+
60+
if (typeof value === 'function') {
61+
redirects[key as keyof typeof this.options.redirect] = value(this, typeof this.ctx.$localePath === 'function' ? this.ctx.$localePath as Function : undefined)
62+
}
6063
}
64+
65+
return redirects;
6166
}
6267

6368
getStrategy(throwException = true): Scheme {
@@ -377,7 +382,7 @@ export class Auth {
377382
return;
378383
}
379384

380-
let to: string = this.options.redirect[name as keyof typeof this.options.redirect];
385+
let to = this.options.redirect[name as keyof typeof this.options.redirect] as string;
381386

382387
if (!to) {
383388
return;

src/runtime/core/middleware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
2828
const { tokenExpired, refreshTokenExpired, isRefreshable } = auth.check(true);
2929

3030
// -- Authorized --
31-
if (!login || insidePage(login) || pageIsInGuestMode) {
31+
if (!login || insidePage(login as string) || pageIsInGuestMode) {
3232
return auth.redirect('home', to)
3333
}
3434

@@ -58,7 +58,7 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
5858
// -- Guest --
5959
// (Those passing `callback` at runtime need to mark their callback component
6060
// with `auth: false` to avoid an unnecessary redirect from callback to login)
61-
else if (!pageIsInGuestMode && (!callback || !insidePage(callback))) {
61+
else if (!pageIsInGuestMode && (!callback || !insidePage(callback as string))) {
6262
return auth.redirect('login', to);
6363
}
6464
});

src/runtime/inc/request-handler.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import type { TokenableScheme, RefreshableScheme } from '../../types';
2+
import type { Auth } from '..'
23
import { ExpiredAuthSessionError } from './expired-auth-session-error';
34
import { FetchInstance, type FetchConfig } from '@refactorjs/ofetch';
45

56
export class RequestHandler {
67
scheme: TokenableScheme | RefreshableScheme;
8+
auth: Auth;
79
http: FetchInstance;
8-
interceptor: number | undefined | null;
10+
requestInterceptor: number | undefined | null;
11+
responseInterceptor: number | undefined | null;
912

10-
constructor(scheme: TokenableScheme | RefreshableScheme, http: FetchInstance) {
13+
constructor(scheme: TokenableScheme | RefreshableScheme, http: FetchInstance, auth: Auth) {
1114
this.scheme = scheme;
1215
this.http = http;
13-
this.interceptor = null;
16+
this.auth = auth;
17+
this.requestInterceptor = null;
18+
this.responseInterceptor = null;
1419
}
1520

1621
setHeader(token: string): void {
@@ -28,7 +33,7 @@ export class RequestHandler {
2833
}
2934

3035
initializeRequestInterceptor(refreshEndpoint?: string | Request): void {
31-
this.interceptor = this.http.interceptors.request.use(
36+
this.requestInterceptor = this.http.interceptors.request.use(
3237
async (config: FetchConfig) => {
3338
// Don't intercept refresh token requests
3439
if ((this.scheme.options.token && !this.#needToken(config)) || config.url === refreshEndpoint) {
@@ -83,12 +88,26 @@ export class RequestHandler {
8388
return this.#getUpdatedRequestConfig(config, token ? token.get() : false);
8489
}
8590
);
91+
this.responseInterceptor = this.http.interceptors.response.use(
92+
res => res,
93+
error => {
94+
if (typeof this.auth.options.resetOnResponseError === 'function') {
95+
this.auth.options.resetOnResponseError(error, this.auth, this.scheme)
96+
}
97+
else if (this.auth.options.resetOnResponseError && error.response.status === 401) {
98+
this.scheme.reset?.()
99+
throw new ExpiredAuthSessionError();
100+
}
101+
}
102+
);
86103
}
87104

88105
reset(): void {
89106
// Eject request interceptor
90-
this.http.interceptors.request.eject(this.interceptor as number);
91-
this.interceptor = null;
107+
this.http.interceptors.request.eject(this.requestInterceptor as number);
108+
this.http.interceptors.response.eject(this.responseInterceptor as number);
109+
this.requestInterceptor = null;
110+
this.responseInterceptor = null;
92111
}
93112

94113
#needToken(config: FetchConfig): boolean {

src/runtime/schemes/local.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export class LocalScheme<OptionsT extends LocalSchemeOptions = LocalSchemeOption
6565
this.token = new Token(this, this.$auth.$storage);
6666

6767
// Initialize Request Interceptor
68-
this.requestHandler = new RequestHandler(this, this.$auth.ctx.$http);
68+
this.requestHandler = new RequestHandler(this, this.$auth.ctx.$http, $auth);
6969
}
7070

7171
check(checkStatus = false): SchemeCheck {
@@ -151,7 +151,7 @@ export class LocalScheme<OptionsT extends LocalSchemeOptions = LocalSchemeOption
151151
this.updateTokens(response);
152152

153153
// Initialize request interceptor if not initialized
154-
if (!this.requestHandler.interceptor) {
154+
if (!this.requestHandler.requestInterceptor) {
155155
this.initializeRequestInterceptor();
156156
}
157157

src/runtime/schemes/oauth2.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class Oauth2Scheme<OptionsT extends Oauth2SchemeOptions = Oauth2SchemeOpt
106106
this.refreshController = new RefreshController(this);
107107

108108
// Initialize Request Handler
109-
this.requestHandler = new RequestHandler(this, this.$auth.ctx.$http);
109+
this.requestHandler = new RequestHandler(this, this.$auth.ctx.$http, $auth);
110110

111111
// Initialize Client Window Reference
112112
this.#clientWindowReference = null;
@@ -123,7 +123,7 @@ export class Oauth2Scheme<OptionsT extends Oauth2SchemeOptions = Oauth2SchemeOpt
123123
}
124124

125125
protected get logoutRedirectURI(): string {
126-
return (this.options.logoutRedirectUri || joinURL(requrl(this.req), this.$auth.options.redirect.logout));
126+
return (this.options.logoutRedirectUri || joinURL(requrl(this.req), this.$auth.options.redirect.logout as string));
127127
}
128128

129129
check(checkStatus = false): SchemeCheck {
@@ -350,7 +350,7 @@ export class Oauth2Scheme<OptionsT extends Oauth2SchemeOptions = Oauth2SchemeOpt
350350
const route = (this.$auth.ctx.$router as Router).currentRoute.value
351351

352352
// Handle callback only for specified route
353-
if (this.$auth.options.redirect && normalizePath(route.path) !== normalizePath(this.$auth.options.redirect.callback)) {
353+
if (this.$auth.options.redirect && normalizePath(route.path) !== normalizePath(this.$auth.options.redirect.callback as string)) {
354354
return;
355355
}
356356

src/runtime/schemes/openIDConnect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export class OpenIDConnectScheme<OptionsT extends OpenIDConnectSchemeOptions = O
178178
const route = (this.$auth.ctx.$router as Router).currentRoute.value;
179179

180180
// Handle callback only for specified route
181-
if (this.$auth.options.redirect && normalizePath(route.path) !== normalizePath(this.$auth.options.redirect.callback)) {
181+
if (this.$auth.options.redirect && normalizePath(route.path) !== normalizePath(this.$auth.options.redirect.callback as string)) {
182182
return;
183183
}
184184

src/types/options.d.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Strategy } from './strategy';
22
import type { NuxtPlugin } from '@nuxt/schema';
3-
import type { AuthState } from './index';
3+
import type { AuthState, RefreshableScheme, TokenableScheme } from './index';
44
import type { CookieSerializeOptions } from 'cookie-es';
5+
import type { Auth } from '../runtime'
56

67
export interface ModuleOptions {
78
globalMiddleware?: boolean;
@@ -10,6 +11,7 @@ export interface ModuleOptions {
1011
strategies?: Record<string, Strategy>;
1112
ignoreExceptions: boolean;
1213
resetOnError: boolean | ((...args: any[]) => boolean);
14+
resetOnResponseError: boolean | ((error: any, auth: Auth, scheme: TokenableScheme | RefreshableScheme) => void);
1315
defaultStrategy: string | undefined;
1416
watchLoggedIn: boolean;
1517
rewriteRedirects: boolean;
@@ -39,10 +41,10 @@ export interface ModuleOptions {
3941
};
4042
}>,
4143
redirect: {
42-
login: string;
43-
logout: string;
44-
callback: string;
45-
home: string;
44+
login: string | ((auth: Auth, trans?: Function) => string);
45+
logout: string | ((auth: Auth, trans?: Function) => string);
46+
callback: string | ((auth: Auth, trans?: Function) => string);
47+
home: string | ((auth: Auth, trans?: Function) => string);
4648
};
4749
initialState?: AuthState;
4850
}

src/utils/provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Oauth2SchemeOptions, RefreshSchemeOptions, LocalSchemeOptions, CookieSchemeOptions } from '../runtime';
1+
import type { Oauth2SchemeOptions, RefreshSchemeOptions } from '../runtime';
22
import type { StrategyOptions, HTTPRequest, TokenableSchemeOptions } from '../types';
33
import type { Nuxt } from '@nuxt/schema';
44
import { addServerHandler, addTemplate } from '@nuxt/kit';

0 commit comments

Comments
 (0)