Skip to content

Commit

Permalink
feat: add maxAge cookies && fix throttle getMe endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
LorexIQ committed Mar 27, 2024
1 parent a203fb4 commit 13982ec
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 70 deletions.
18 changes: 4 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 0 additions & 9 deletions playground/app.vue

This file was deleted.

13 changes: 11 additions & 2 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
const env = {
serverOrigin: process.env.SERVER_ORIGIN ?? '/'
};

export default defineNuxtConfig({
ssr: false,
modules: ['../src/module'],
//modules: ['nuxt-local-auth'],

runtimeConfig: {
public: {
...env
}
},

localAuth: {
origin: 'https://catman-dev.atrinix.ru/api/v1/',
origin: env.serverOrigin,
token: {
lifetime: 60 * 60 * 24,
path: 'access',
queryKey: 'token'
},
sessions: {
path: 'data',
refreshEvery: 5000
},
refreshToken: {
Expand Down
4 changes: 3 additions & 1 deletion playground/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ definePageMeta({

<template>
<div>
{{auth}}
<div>
{{auth}}
</div>
<button @click="() => auth.signOut()">testSignOut</button>
<button @click="() => auth.refreshToken()">testRefreshToken</button>
<button @click="() => auth.refreshTokenWithCheck()">testRefreshWithCheckToken</button>
Expand Down
2 changes: 1 addition & 1 deletion src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default defineNuxtModule<ModuleOptions>({
protectAllPages: false,
}
},
setup (options, nuxt) {
setup: function (options, nuxt) {
const resolver = createResolver(import.meta.url);
nuxt.options.runtimeConfig.public.localAuth = defu(
nuxt.options.runtimeConfig.public.localAuth,
Expand Down
34 changes: 18 additions & 16 deletions src/runtime/composables/useLocalAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { LocalAuthError } from '../errors';
import { fetch, getContext } from '../helpers';
import useLocalAuthState from './useLocalAuthState';

const u = undefined;

async function signIn<T extends UseLocalAuthResponse = {}>(data: UseLocalAuthData, config?: UseLocalAuthConfig): Promise<T> {
const { options, state: { saveMeta } } = await getContext();
const router = useRouter();
Expand All @@ -26,7 +28,7 @@ async function signIn<T extends UseLocalAuthResponse = {}>(data: UseLocalAuthDat

return authData;
} catch (e: any) {
if (e.response) throw new LocalAuthError(`signIn > [${e.statusCode}] > ${JSON.stringify(e.response._data)}`);
if (e.response) throw new LocalAuthError(e, `[${e.statusCode}] > ${JSON.stringify(e.response._data)}`, signIn.name);
else throw new LocalAuthError(e.message);
}
}
Expand All @@ -35,7 +37,7 @@ async function signUp<T extends UseLocalAuthResponse = {}>(data: UseLocalAuthDat
const router = useRouter();
const endpointConfig = options.endpoints.signUp!;

if (!endpointConfig) throw new LocalAuthError('signUp > signUp is disabled. Enable it in endpoints/signUp');
if (!endpointConfig) throw new LocalAuthError(u, 'signUp is disabled. Enable it in endpoints/signUp', signUp.name);

try {
const signUpData = await fetch<T>(endpointConfig, { body: data });
Expand All @@ -46,7 +48,7 @@ async function signUp<T extends UseLocalAuthResponse = {}>(data: UseLocalAuthDat

return signUpData;
} catch (e: any) {
if (e.response) throw new LocalAuthError(`signUp > [${e.statusCode}] > ${JSON.stringify(e.response._data)}`);
if (e.response) throw new LocalAuthError(e, `[${e.statusCode}] > ${JSON.stringify(e.response._data)}`, signUp.name);
else throw new LocalAuthError(e.message);
}
}
Expand All @@ -59,7 +61,7 @@ async function signOut<T extends UseLocalAuthResponse = {}>(config?: UseLocalAut
try {
return await fetch<T>(endpointConfig, { withToken: true });
} catch (e: any) {
throw new LocalAuthError(`signOut > [${e.statusCode}] > ${JSON.stringify(e.response._data)}`);
throw new LocalAuthError(e, `[${e.statusCode}] > ${JSON.stringify(e.response._data)}`, signOut.name);
} finally {
clearMeta();
await router.push(config?.redirectTo ?? options.pages.auth!);
Expand All @@ -74,11 +76,11 @@ async function getMe<T extends UseLocalAuthResponse = {}>(): Promise<T> {
const { options, state: { token, saveSession } } = await getContext();
const endpointConfig = options.endpoints.getMe!;

if (!token.value) throw new LocalAuthError('getMe > token is null. SignIn first');
if (!token.value) throw new LocalAuthError(u, 'token is null. SignIn first', getMe.name);

try {
await refreshTokenWithCheck();
} catch (e) {}
} catch (e) { /* empty */ }

try {
const meData = await fetch<T>(endpointConfig, { withToken: true });
Expand All @@ -89,9 +91,9 @@ async function getMe<T extends UseLocalAuthResponse = {}>(): Promise<T> {
} catch (e: any) {
if (e.statusCode) {
await signOut();
throw new LocalAuthError(`getMe > [${e.statusCode}] > ${JSON.stringify(e.response._data)}`);
throw new LocalAuthError(e, `[${e.statusCode}] > ${JSON.stringify(e.response._data)}`, 'getMe');
} else {
throw new LocalAuthError(`getMe > ${e.message}`);
throw new LocalAuthError(e, `getMe > ${e.message}`);
}
}
}
Expand All @@ -101,7 +103,7 @@ async function refreshToken<T extends UseLocalAuthResponse = {}>(): Promise<T> {
const refreshConfig = options.refreshToken;

if (refreshConfig.enabled) {
if (!meta.value.refreshToken) throw new LocalAuthError(`refreshToken > refreshToken is null`);
if (!meta.value.refreshToken) throw new LocalAuthError(u, `refreshToken is null`, refreshToken.name);

try {
const refreshData = await fetch<T>(endpointConfig, {
Expand All @@ -118,18 +120,18 @@ async function refreshToken<T extends UseLocalAuthResponse = {}>(): Promise<T> {
return refreshData;
} catch (e: any) {
await signOut();
throw new LocalAuthError(`refreshToken > [${e.statusCode}] > ${JSON.stringify(e.response._data)}`);
throw new LocalAuthError(e, `[${e.statusCode}] > ${JSON.stringify(e.response._data)}`, refreshToken.name);
}

} else {
throw new LocalAuthError('refreshToken > refresh token is disabled. Enable it in refreshToken/enabled');
throw new LocalAuthError(u, 'refresh token is disabled. Enable it in refreshToken/enabled', refreshToken.name);
}
}
async function refreshTokenWithCheck<T extends UseLocalAuthResponse = {}>(): Promise<T | null> {
const { options: { refreshToken: refreshTokenConfig }, state: { meta } } = await getContext();
const metaData = meta.value;

if (!metaData.refreshToken) throw new LocalAuthError(`refreshTokenWithCheck > refreshToken is null`);
if (!metaData.refreshToken) throw new LocalAuthError(u, `refreshToken is null`, refreshTokenWithCheck.name);

try {
if (!refreshTokenConfig.enabled) throw Error('refresh token is disabled. Enable it in refreshToken/enabled');
Expand All @@ -138,7 +140,7 @@ async function refreshTokenWithCheck<T extends UseLocalAuthResponse = {}>(): Pro

return await refreshToken<T>();
} catch (e: any) {
throw new LocalAuthError(`refreshTokenWithCheck > ${e.message}`);
throw new LocalAuthError(e, `${e.message}`, refreshTokenWithCheck.name);
}
}
async function checkAndSaveQueryAuth(): Promise<void> {
Expand All @@ -149,14 +151,14 @@ async function checkAndSaveQueryAuth(): Promise<void> {
const isTokenReading = options.token.queryKey;
const isRefreshTokenReading = options.refreshToken.queryKey;

if (!isTokenReading) throw new LocalAuthError('checkAndSaveQueryAuth > token is not configure. Set key in token/queryKey');
if (!isTokenReading) throw new LocalAuthError(u, 'token is not configure. Set key in token/queryKey', checkAndSaveQueryAuth.name);

try {
const token = query[isTokenReading] ?? null;
let refreshToken = query[isRefreshTokenReading!] ?? null;
const refreshToken = query[isRefreshTokenReading!] ?? null;

if (token) softSaveMeta(token as string, refreshToken as string | null);
} catch (e: any) {}
} catch (e: any) { /* empty */ }
}

export function useLocalAuth(): UseLocalAuthReturn {
Expand Down
25 changes: 14 additions & 11 deletions src/runtime/composables/useLocalAuthState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function () {
const options = useRuntimeConfig().public.localAuth as ModuleOptions;

const sessionData = useState<UseLocalAuthCredentials>('localAuth:Credentials', () => ({}));
const cookieData = useState('localAuth:Cookie', () => useCookie<UseLocalAuthSession | null>(options.sessions.cookiePrefix!, { default: () => null }));
let cookieData = useState('localAuth:Cookie', () => useCookie<UseLocalAuthSession | null>(options.sessions.cookiePrefix!, { default: () => null }));
const sessionMetaInfo: Ref<UseLocalAuthSession> = useState('localAuth:Meta', () => ({
token: null,
refreshToken: null,
Expand All @@ -25,6 +25,10 @@ export default function () {

useState('localAuth:CookieWatcher', () => watch(sessionMetaInfo, (value, oldValue) => {
if (oldValue) {
cookieData = useCookie<UseLocalAuthSession | null>(options.sessions.cookiePrefix!, {
default: () => null,
maxAge: value.exp! - Math.round(Date.now() / 1000)
});
cookieData.value = value.token ? value : null;
} else {
try {
Expand All @@ -36,7 +40,7 @@ export default function () {
value.exp = decodeCookie.exp;
value.status = decodeCookie.status;
}
} catch (e) {}
} catch (e) { /* empty */ }
}
}, { immediate: true }));

Expand Down Expand Up @@ -76,15 +80,14 @@ export default function () {

sessionMetaInfo.value.token = token;
sessionMetaInfo.value.refreshToken = refreshToken;
sessionMetaInfo.value.exp = `${Math.round(Date.now() / 1000) + (options.token.lifetime as number)}`;
sessionMetaInfo.value.exp = Math.round(Date.now() / 1000) + (options.token.lifetime as number);
}
function saveMeta(data: UseLocalAuthResponse, metaUpdate: boolean = false): void {
metaUpdate ? softClearMeta() : clearMeta();
let parsedToken: string | undefined,
parsedRefreshToken: string | undefined,
parsedLifetime: string | undefined;
let parsedRefreshToken: string | undefined,
parsedLifetime: number | undefined;

parsedToken = parseValueWithPath(data, options.token.path);
const parsedToken = parseValueWithPath(data, options.token.path);
if (!parsedToken) throw new Error('error parse auth token. Check current token/path');

if (options.refreshToken.enabled) {
Expand All @@ -93,16 +96,16 @@ export default function () {
}

const lifetime = options.token.lifetime as string | number;
if (typeof options.token.lifetime === 'string') {
parsedLifetime = parseValueWithPath(data, options.token.lifetime!);
if (typeof lifetime === 'string') {
parsedLifetime = parseValueWithPath<number>(data, lifetime);
if (!parsedLifetime) throw new Error('error parse lifetime token. Check current token/lifetime');
} else {
parsedLifetime = `${Math.round(Date.now() / 1000) + (lifetime as number)}`;
parsedLifetime = Math.round(Date.now() / 1000) + lifetime;
}

sessionMetaInfo.value.token = parsedToken as string;
sessionMetaInfo.value.refreshToken = parsedRefreshToken as string;
sessionMetaInfo.value.exp = `${parsedLifetime}`;
sessionMetaInfo.value.exp = parsedLifetime;
}
function clearSession(): void {
sessionMetaInfo.value.status = 'unauthorized';
Expand Down
11 changes: 7 additions & 4 deletions src/runtime/errors.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export class LocalAuthError extends Error {
constructor(message: string = '') {
super(message);
this.name = 'LocalAuthError';
}
error?: Error;

constructor(e?: Error, message?: string, handler?: string) {
super((handler ? (handler + (message ? ': ' : '')) : '') + message);
this.name = 'LocalAuthError';
this.error = e;
}
}
5 changes: 2 additions & 3 deletions src/runtime/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,14 @@ export async function fetch<T extends UseLocalAuthResponse>(

try {
return await $fetch(
`${origin}/${trimStartWithSymbol(endpoint.path, '/')}`,
`${origin}/${trimStartWithSymbol(endpoint!.path, '/')}`,
{
method: endpoint.method,
method: endpoint!.method,
body: config.body,
headers: config.withToken ? { 'Authorization': token.value! } : {}
}
);
} catch (e: any) {
console.log(options)
if (!e.statusCode && options.pages.serverIsDown) {
meta.value.status = 'timeout';
navigateTo(options.pages.serverIsDown);
Expand Down
14 changes: 8 additions & 6 deletions src/runtime/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ export default defineNuxtPlugin(async () => {
let pendingInterval: NodeJS.Timeout;

try {
if (options.token.queryKey) await checkAndSaveQueryAuth();
} catch (e) {}
if (options.token.queryKey) {
await checkAndSaveQueryAuth();
}
} catch (e) { /* empty */ }
try {
if (token.value) await getMe();
} catch (e) {
console.error(e);
}
if (token.value) {
await getMe();
}
} catch (e) { /* empty */ }

if (options.sessions.refreshEvery) {
watch(meta, value => {
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export type UseLocalAuthStatus =
export interface UseLocalAuthSession {
token: string | null;
refreshToken: string | null;
exp: string | null;
exp: number | null;
status: UseLocalAuthStatus;
}
export interface UseLocalAuthFetchConfig {
Expand Down Expand Up @@ -128,12 +128,12 @@ interface ModuleOptionsEndpoints {
* path: 'auth/signIn'
* method: 'POST'
* */
signIn: ModuleOptionsEndpointConfig;
signIn?: ModuleOptionsEndpointConfig;
/* Get authorized user info config. Default:
* path: 'users/me'
* method: 'GET'
* */
getMe: ModuleOptionsEndpointConfig;
getMe?: ModuleOptionsEndpointConfig;
/* Refresh token config. Default:
* path: 'auth/refresh
* method: 'POST'
Expand Down

0 comments on commit 13982ec

Please sign in to comment.