Skip to content

Commit

Permalink
feat: desctruct project and add pending current session
Browse files Browse the repository at this point in the history
  • Loading branch information
LorexIQ committed Nov 22, 2023
1 parent 4a8cf36 commit ae8359d
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 72 deletions.
3 changes: 3 additions & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export default defineNuxtConfig({
lifetime: 5,
path: 'access'
},
sessions: {
refreshEvery: 5000
},
refreshToken: {
enabled: true
},
Expand Down
3 changes: 2 additions & 1 deletion playground/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ definePageMeta({
<div>
{{auth}}
<button @click="() => auth.signOut()">testSignOut</button>
<button @click="() => auth.refreshTokenWithCheck()">testRefreshToken</button>
<button @click="() => auth.refreshToken()">testRefreshToken</button>
<button @click="() => auth.refreshTokenWithCheck()">testRefreshWithCheckToken</button>
</div>
</template>
27 changes: 21 additions & 6 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@nuxt/kit'
import defu from "defu";

interface ModuleOptionsEndpointConfig {
export interface ModuleOptionsEndpointConfig {
path: string;
method: 'POST' | 'GET';
}
Expand Down Expand Up @@ -66,24 +66,36 @@ interface ModuleOptionsRefreshToken {
* */
bodyKey?: string;
}
interface ModuleOptionsSession {
/* Enabled refresh user data every N ms. Default: undefined
* Example: one getMe request in 5 seconds. value > 5000
* Example: disable refresh. value > undefined
* */
refreshEvery?: number;
/* Cookie prefix in app storage. Default: localAuth
* Example: localAuth:token -> {token}
* */
cookiePrefix?: string;
}
interface ModuleOptionsPages {
/* Page for authorization in the system. Default: '/login'
* */
auth?: string;
/* The standard page where to redirect the user after logging in. Default: '/'
* */
defaultRedirect?: string;
/* A page for catching a server shutdown when it is impossible to
* get session data due to a timeout. Default: '/error'
* */
serverIsDown?: string;
}
export interface ModuleOptions {
/* Path to server. Default: ''
* Example: 'http://localhost:8000/api/v1' - remote server
* Example: 'api' - local nuxt server
* */
origin: string;
/* Cookie prefix in app storage. Default: localAuth
* Example: localAuth:token -> {token}
* */
cookiePrefix?: string;
sessions: ModuleOptionsSession;
token: ModuleOptionsToken;
refreshToken: ModuleOptionsRefreshToken;
endpoints: ModuleOptionsEndpoints;
Expand All @@ -97,7 +109,10 @@ export default defineNuxtModule<ModuleOptions>({
},
defaults: {
origin: '',
cookiePrefix: 'localAuth',
sessions: {
refreshEvery: undefined,
cookiePrefix: 'localAuth',
},
token: {
lifetime: 86400,
path: 'token',
Expand Down
93 changes: 31 additions & 62 deletions src/runtime/composables/useLocalAuth.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,21 @@
import { useRouter, useNuxtApp, callWithNuxt, useRuntimeConfig } from '#app';
import { computed } from 'vue';
import useLocalAuthState from './useLocalAuthState';
import { LocalAuthError } from '../errors';
import type {ModuleOptions} from "../../module";
import type {
UseLocalAuthData,
UseLocalAuthConfig
UseLocalAuthConfig,
UseLocalAuthResponse
} from '../types';
import useUtils from "./useUtils";

const { trimStartWithSymbol } = useUtils();

async function getContext() {
const nuxt = useNuxtApp();
const options = (await callWithNuxt(nuxt, useRuntimeConfig)).public.localAuth as ModuleOptions;
const state = await callWithNuxt(nuxt, useLocalAuthState);

return {
options,
state
};
}
import { useRouter } from '#app';
import { computed } from 'vue';
import { LocalAuthError } from '../errors';
import { fetch, getContext } from '../helpers';
import useLocalAuthState from './useLocalAuthState';

async function signIn<T = void>(data: UseLocalAuthData, config?: UseLocalAuthConfig): Promise<T> {
const { options, state: { origin, saveSession } } = await getContext();
async function signIn<T extends UseLocalAuthResponse = {}>(data: UseLocalAuthData, config?: UseLocalAuthConfig): Promise<T> {
const { options, state: { saveSession } } = await getContext();
const router = useRouter();
const endpointConfig = options.endpoints.signIn!;

try {
const authData = await $fetch(
`${origin}/${trimStartWithSymbol(endpointConfig.path, '/')}`,
{
method: endpointConfig.method,
body: data
}
);
const authData = await fetch<T>(endpointConfig, { body: data });

saveSession(authData);
await getMe();
Expand All @@ -46,19 +27,14 @@ async function signIn<T = void>(data: UseLocalAuthData, config?: UseLocalAuthCon
else throw new LocalAuthError(e.message);
}
}
async function signOut(config?: UseLocalAuthConfig): Promise<void> {
const { options, state: { origin, clearSession } } = await getContext();
async function signOut<T extends UseLocalAuthResponse = {}>(config?: UseLocalAuthConfig): Promise<T> {
const { options, state: { clearSession } } = await getContext();
const router = useRouter();
const endpointConfig = options.endpoints.signOut!;

if (options.endpoints.signOut) {
try {
await $fetch(
`${origin}/${trimStartWithSymbol(endpointConfig.path, '/')}`,
{
method: endpointConfig.method
}
);
return await fetch<T>(endpointConfig, { withToken: true });
} catch (e: any) {
throw new LocalAuthError(`signOut > [${e.statusCode}] > ${JSON.stringify(e.response._data)}`);
} finally {
Expand All @@ -68,49 +44,42 @@ async function signOut(config?: UseLocalAuthConfig): Promise<void> {
} else {
clearSession();
await router.push(config?.redirectTo ?? options.pages.auth!);
return {} as T;
}
}
async function getMe<T>(): Promise<T> {
const { options, state: { token, origin, data, meta } } = await getContext();
async function getMe<T extends UseLocalAuthResponse = {}>(): Promise<T> {
const { options, state: { token, data, meta } } = await getContext();
const endpointConfig = options.endpoints.getMe!;

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

try {
const meData = await $fetch(
`${origin}/${trimStartWithSymbol(endpointConfig.path, '/')}`,
{
method: endpointConfig.method,
headers: {
'Authorization': token.value
}
}
);
const meData = await fetch<T>(endpointConfig, { withToken: true });

Object.assign(data.value, meData);
meta.value.status = 'authorized';

return meData;
} catch (e: any) {
throw new LocalAuthError(`getMe > [${e.statusCode}] > ${JSON.stringify(e.response._data)}`);
if (e.statusCode) {
throw new LocalAuthError(`getMe > [${e.statusCode}] > ${JSON.stringify(e.response._data)}`);
} else {
throw new LocalAuthError(`getMe > ${e.message}`);
}
}
}
async function refreshToken<T>(): Promise<T> {
const { options, state: { origin, meta, saveSession } } = await getContext();
async function refreshToken<T extends UseLocalAuthResponse = {}>(): Promise<T> {
const { options, state: { meta, saveSession } } = await getContext();
const endpointConfig = options.endpoints.refreshToken!;
const refreshConfig = options.refreshToken;

if (refreshConfig.enabled) {
try {
const refreshData = await $fetch(
`${origin}/${trimStartWithSymbol(endpointConfig.path, '/')}`,
{
method: endpointConfig.method,
body: {
[`${refreshConfig.bodyKey}`]: meta.value.refreshToken
}
const refreshData = await fetch<T>(endpointConfig, {
body: {
[`${refreshConfig.bodyKey}`]: meta.value.refreshToken!
}
);
});

saveSession({
[`${refreshConfig.path}`]: meta.value.refreshToken,
Expand All @@ -126,7 +95,7 @@ async function refreshToken<T>(): Promise<T> {
throw new LocalAuthError('refreshToken > refresh token is disabled. Enable it in refreshToken/enabled');
}
}
async function refreshTokenWithCheck<T>(): Promise<T | null> {
async function refreshTokenWithCheck<T extends UseLocalAuthResponse = {}>(): Promise<T | null> {
const { options: { refreshToken: refreshTokenConfig }, state: { meta } } = await getContext();
const metaData = meta.value;

Expand All @@ -135,7 +104,7 @@ async function refreshTokenWithCheck<T>(): Promise<T | null> {
if (metaData.status !== 'authorized') throw Error('session is not found. Use signIn');
if (Date.now() < +meta.value.exp! * 1000) return null;

return await refreshToken();
return await refreshToken<T>();
} catch (e: any) {
throw new LocalAuthError(`refreshTokenWithCheck > ${e.message}`);
}
Expand Down
2 changes: 1 addition & 1 deletion 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>('localAuth', { default: () => null }));
const cookieData = useState('localAuth:Cookie', () => useCookie<UseLocalAuthSession | null>(options.sessions.cookiePrefix!, { default: () => null }));
const sessionMetaInfo: Ref<UseLocalAuthSession> = useState('localAuth:Meta', () => ({
token: null,
refreshToken: null,
Expand Down
37 changes: 37 additions & 0 deletions src/runtime/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type {ModuleOptions, ModuleOptionsEndpointConfig} from "../module";
import type {UseLocalAuthFetchConfig, UseLocalAuthResponse} from "./types";
import {callWithNuxt, useNuxtApp, useRuntimeConfig} from "#app";
import useLocalAuthState from "./composables/useLocalAuthState";
import useUtils from "./composables/useUtils";

const { trimStartWithSymbol } = useUtils();

export async function getContext() {
const nuxt = useNuxtApp();
const options = (await callWithNuxt(nuxt, useRuntimeConfig)).public.localAuth as ModuleOptions;
const state = await callWithNuxt(nuxt, useLocalAuthState);

return {
options,
state
};
}
export async function fetch<T extends UseLocalAuthResponse>(
endpoint: ModuleOptionsEndpointConfig,
_config: UseLocalAuthFetchConfig
): Promise<T> {
const config: UseLocalAuthFetchConfig = {
withToken: false,
..._config,
}
const { state: { token, origin} } = await getContext();

return await $fetch(
`${origin}/${trimStartWithSymbol(endpoint.path, '/')}`,
{
method: endpoint.method,
body: config.body,
headers: config.withToken ? { 'Authorization': token.value! } : {}
}
);
}
19 changes: 17 additions & 2 deletions src/runtime/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
import { defineNuxtPlugin, addRouteMiddleware } from '#app'
import { useLocalAuth } from '#imports'
import { useLocalAuth, watch } from '#imports'
import { getContext } from './helpers';
import auth from "./middleware/auth";

export default defineNuxtPlugin(async () => {
const { token, getMe } = useLocalAuth()
const { options, state: { token } } = await getContext();
const { getMe } = useLocalAuth();
let pendingInterval: NodeJS.Timeout;

try {
if (token.value) await getMe();
} catch (e) {}

if (options.sessions.refreshEvery) {
watch(token, value => {
clearInterval(pendingInterval);
if (value) {
pendingInterval = setInterval(async () => {
if (token.value) await getMe();
else clearInterval(pendingInterval);
}, options.sessions.refreshEvery);
}
}, { immediate: true });
}

addRouteMiddleware('auth', auth, {
global: true
});
Expand Down
4 changes: 4 additions & 0 deletions src/runtime/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ export interface UseLocalAuthSession {
exp: string | null;
status: UseLocalAuthStatus;
}
export interface UseLocalAuthFetchConfig {
withToken?: boolean;
body?: UseLocalAuthData;
}

0 comments on commit ae8359d

Please sign in to comment.