Skip to content

Commit

Permalink
fix: update token expire time feature bug
Browse files Browse the repository at this point in the history
  • Loading branch information
ronger-x committed May 2, 2024
1 parent e00e0e4 commit fbc6fc5
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 107 deletions.
18 changes: 15 additions & 3 deletions src/runtime/composables/local/use-auth-state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ComputedRef, computed, watch } from 'vue'
import { type ComputedRef, type Ref, computed, watch } from 'vue'

import { useCommonAuthState } from '../use-common-auth-state'
import { useTypedConfig } from '../../utils/helper'
Expand All @@ -9,6 +9,8 @@ type UseAuthStateReturn = {
token: ComputedRef<string | null>
setToken: (value: string | null) => void
clearToken: () => void
tokenExpiredTime: Ref<Date | null>,
setTokenExpiredTime: (value: Date | null) => void
} & ReturnType<typeof useCommonAuthState<SessionData>>

export const useAuthState = (): UseAuthStateReturn => {
Expand Down Expand Up @@ -37,20 +39,30 @@ export const useAuthState = (): UseAuthStateReturn => {
return config.token.type.length > 0 ? `${config.token.type} ${rawToken.value}` : rawToken.value
})

const tokenExpiredTime = useState('auth:token-expired-time', () => {
if (rawToken.value === null) return null
return new Date(Date.now() + config.token.maxAgeInSeconds * 1000)
})

const setToken = (value: string | null) => {
rawToken.value = value
commonAuthState.tokenExpiredTime.value =
value === null ? null : new Date(new Date().getTime() + config.token.maxAgeInSeconds * 1000)
}

const setTokenExpiredTime = (value: Date | null) => {
tokenExpiredTime.value = value;
}

const clearToken = () => {
setToken(null)
setTokenExpiredTime(null)
}

return {
...commonAuthState,
token,
setToken,
clearToken,
tokenExpiredTime,
setTokenExpiredTime,
}
}
1 change: 1 addition & 0 deletions src/runtime/composables/local/use-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const signIn: SignIn<Credentials, any> = async (credentials, signInOptions = {},
}

useAuthState().setToken(token)
useAuthState().setTokenExpiredTime(new Date(Date.now() + config.token.maxAgeInSeconds * 1000))
await getSession()

const { redirect = true, callbackUrl, external } = signInOptions
Expand Down
231 changes: 133 additions & 98 deletions src/runtime/composables/refresh/use-auth.ts
Original file line number Diff line number Diff line change
@@ -1,209 +1,244 @@
import { defu } from 'defu'
import { defu } from 'defu';

import type { Refresh } from '../../types'
import { jsonPointerGet, useTypedConfig } from '../../utils/helper'
import { useAuthFetch } from '../use-auth-fetch'
import { logger } from '../../utils/logger'
import { useAuth as useLocalAuth } from '../local/use-auth'
import { useAuthState } from './use-auth-state'
import { navigateTo, useRuntimeConfig } from '#imports'
import type { Refresh } from '../../types';
import { jsonPointerGet, useTypedConfig } from '../../utils/helper';
import { useAuthFetch } from '../use-auth-fetch';
import { logger } from '../../utils/logger';
import { useAuth as useLocalAuth } from '../local/use-auth';
import { useAuthState } from './use-auth-state';
import { navigateTo, useRuntimeConfig } from '#imports';
import type { SessionData } from '#auth';

/**
* 请求用户权限数据
*/
const getSession: ReturnType<typeof useLocalAuth>['getSession'] = async (
getSessionOptions = {},
const getSession: ReturnType<typeof useLocalAuth>["getSession"] = async (
getSessionOptions = {}
) => {
const { token, tokenExpiredTime, refreshToken } = useAuthState()
const config = useTypedConfig(useRuntimeConfig(), "refresh");
const { path, method } = config.endpoints.getSession;
const { token, tokenExpiredTime, refreshToken, loading, data, lastRefreshedAt, clearToken } = useAuthState();
if (!token.value && !getSessionOptions.force) {
return
return;
}
console.log('value: ', token.value, refreshToken.value);
if (token.value && refreshToken.value) {
const isTokenExpired = tokenExpiredTime.value === null || Date.now() - tokenExpiredTime.value.getTime() > 0
const isTokenExpired = tokenExpiredTime.value === null || Date.now() - tokenExpiredTime.value.getTime() > 1000;
console.log('isTokenExpired: ', isTokenExpired);
if (isTokenExpired) {
await refresh({ refreshToken: refreshToken.value })
return
await refresh({ refreshToken: refreshToken.value });
return;
}
}
const { getSession } = useLocalAuth()
return await getSession()
}

const headers = new Headers(token.value ? { [config.token.headerName]: token.value } : undefined);

loading.value = true;

try {
const response = await useAuthFetch<Record<string, any>>(path, undefined, method, { headers });

// 根据 JSON pointer 获取正确的 sessionData
data.value = jsonPointerGet(response, config.sessionData.sessionPointer) as SessionData;
} catch (error) {
// 获取 sessionData 出错需要重置登录状态
logger.error(error);
data.value = null;
clearToken();
}

loading.value = false;
lastRefreshedAt.value = new Date();

// 获取用户信息失败后可以进行的操作
const { required = false, callbackUrl, external, onUnauthenticated } = getSessionOptions;

if (required && data.value === null) {
if (onUnauthenticated) {
return onUnauthenticated();
} else {
await navigateTo(callbackUrl ?? "/", { external });
}
}

return data.value;
};
/**
* 登录
*/
const signIn: ReturnType<typeof useLocalAuth>['signIn'] = async (
const signIn: ReturnType<typeof useLocalAuth>["signIn"] = async (
credentials,
signInOptions = {},
fetchOptions,
fetchOptions
) => {
// 1. 获取 refresh 配置
const config = useTypedConfig(useRuntimeConfig(), 'refresh')
const config = useTypedConfig(useRuntimeConfig(), "refresh");

// 2. 发起 signIn 请求
const { path, method } = config.endpoints.signIn
const { path, method } = config.endpoints.signIn;

const response = await useAuthFetch(path, credentials, method, fetchOptions)
const response = await useAuthFetch(path, credentials, method, fetchOptions);

// 3. 获取 token
const expectedToken = jsonPointerGet(response, config.token.signInResponseTokenPointer)
const expectedToken = jsonPointerGet(response, config.token.signInResponseTokenPointer);

if (typeof expectedToken !== 'string') {
if (typeof expectedToken !== "string") {
logger.error(
`Auth: string token expected, received instead: ${JSON.stringify(
expectedToken,
expectedToken
)}. Tried to find token at ${config.token.signInResponseTokenPointer} in ${JSON.stringify(
response,
)}`,
)
return
response
)}`
);
return;
}

// 4. 获取 refreshToken
const expectedRefreshToken = jsonPointerGet(
response,
config.refreshToken.signInResponseRefreshTokenPointer,
)
config.refreshToken.signInResponseRefreshTokenPointer
);

if (typeof expectedRefreshToken !== 'string') {
if (typeof expectedRefreshToken !== "string") {
logger.error(
`Auth: string token expected, received instead: ${JSON.stringify(
expectedRefreshToken,
expectedRefreshToken
)}. Tried to find token at ${
config.refreshToken.signInResponseRefreshTokenPointer
} in ${JSON.stringify(response)}`,
)
return
} in ${JSON.stringify(response)}`
);
return;
}

// 5. 设置 token 与 refreshToken 并获取 sessionData
const { setToken, setRefreshToken } = useAuthState()
const { getSession } = useLocalAuth()
const { setToken, setRefreshToken, setTokenExpiredTime } = useAuthState();

setToken(expectedToken)
setRefreshToken(expectedRefreshToken)
await getSession()
setToken(expectedToken);
setTokenExpiredTime(new Date(Date.now() + config.token.maxAgeInSeconds * 1000))
setRefreshToken(expectedRefreshToken);
await getSession();

// 6. 上述成功后是否重定向
const { redirect = true, callbackUrl, external } = signInOptions
const { redirect = true, callbackUrl, external } = signInOptions;

if (redirect) {
return navigateTo(callbackUrl ?? '/', { external })
return navigateTo(callbackUrl ?? "/", { external });
}
}
};

/**
* 登出
*/
const signOut: ReturnType<typeof useLocalAuth>['signOut'] = async (
const signOut: ReturnType<typeof useLocalAuth>["signOut"] = async (
signOutOptions = {},
fetchOptions,
fetchOptions
) => {
// 1. 获取 signOut endpoint 配置
const config = useTypedConfig(useRuntimeConfig(), 'refresh')
const signOutConfig = config.endpoints.signOut
const { token, data, lastRefreshedAt, clearToken, clearRefreshToken } = useAuthState()
let response
const config = useTypedConfig(useRuntimeConfig(), "refresh");
const signOutConfig = config.endpoints.signOut;
const { token, data, lastRefreshedAt, clearToken, clearRefreshToken } = useAuthState();
let response;

// 2. 发起 signOut 请求
if (signOutConfig) {
const { path, method } = signOutConfig
const { path, method } = signOutConfig;
const headers = new Headers(
token.value ? { [config.token.headerName]: token.value } : undefined,
)
token.value ? { [config.token.headerName]: token.value } : undefined
);

response = await useAuthFetch(path, undefined, method, defu(fetchOptions, { headers }))
response = await useAuthFetch(path, undefined, method, defu(fetchOptions, { headers }));
}

// 3. 清理 token, refreshToken, session
data.value = null
lastRefreshedAt.value = null
clearToken()
clearRefreshToken()
data.value = null;
lastRefreshedAt.value = null;
clearToken();
clearRefreshToken();

// 4. signOut 之后是否需要重定向到其他地址
const { redirect = true, callbackUrl, external } = signOutOptions
const { redirect = true, callbackUrl, external } = signOutOptions;

if (redirect) {
await navigateTo(callbackUrl ?? config.pages.login, { external })
await navigateTo(callbackUrl ?? config.pages.login, { external });
}

return response
}
return response;
};

/**
* 刷新 token
*/
const refresh: Refresh<Record<string, any>> = async (credentials, fetchOptions) => {
// 1. 获取 refresh 请求配置
const config = useTypedConfig(useRuntimeConfig(), 'refresh')
const { path, method } = config.endpoints.refresh
const { token, lastRefreshedAt, setToken, setRefreshToken } = useAuthState()
const { getSession } = useLocalAuth()
const config = useTypedConfig(useRuntimeConfig(), "refresh");
const { path, method } = config.endpoints.refresh;
const { token, lastRefreshedAt, setToken, setRefreshToken, setTokenExpiredTime } = useAuthState();

// 2. 发送 refresh 请求
const headers = new Headers(token.value ? { [config.token.headerName]: token.value } : undefined)
const headers = new Headers(token.value ? { [config.token.headerName]: token.value } : undefined);

const response = await useAuthFetch(path, credentials, method, defu(fetchOptions, { headers }))
const response = await useAuthFetch(path, credentials, method, defu(fetchOptions, { headers }));

// 3. 获取新 token
const expectedToken = jsonPointerGet(response, config.token.signInResponseTokenPointer)
const expectedToken = jsonPointerGet(response, config.token.signInResponseTokenPointer);

if (typeof expectedToken !== 'string') {
if (typeof expectedToken !== "string") {
logger.error(
`Auth: string token expected, received instead: ${JSON.stringify(
expectedToken,
expectedToken
)}. Tried to find token at ${config.token.signInResponseTokenPointer} in ${JSON.stringify(
response,
)}`,
)
return
response
)}`
);
return;
}

// 4. 获取新 refreshToken
if (!config.refreshOnlyToken) {
const expectedRefreshToken = jsonPointerGet(
response,
config.refreshToken.signInResponseRefreshTokenPointer,
)
config.refreshToken.signInResponseRefreshTokenPointer
);

if (typeof expectedRefreshToken !== 'string') {
if (typeof expectedRefreshToken !== "string") {
logger.error(
`Auth: string token expected, received instead: ${JSON.stringify(
expectedRefreshToken,
expectedRefreshToken
)}. Tried to find token at ${
config.refreshToken.signInResponseRefreshTokenPointer
} in ${JSON.stringify(response)}`,
)
return
} in ${JSON.stringify(response)}`
);
return;
} else {
setRefreshToken(expectedRefreshToken)
setRefreshToken(expectedRefreshToken);
}
}

// 5. 设置新 token 并更新时间
setToken(expectedToken)
setToken(expectedToken);
setTokenExpiredTime(new Date(Date.now() + config.token.maxAgeInSeconds * 1000))

await getSession()
lastRefreshedAt.value = new Date()
}
await getSession();
lastRefreshedAt.value = new Date();
};

type UseAuthReturn = ReturnType<typeof useAuthState> &
ReturnType<typeof useLocalAuth> & {
refresh: typeof refresh
}
refresh: typeof refresh
}

export const useAuth = (): UseAuthReturn => {
const localAuth = useLocalAuth()
const localAuth = useLocalAuth();

localAuth.getSession = getSession
localAuth.signIn = signIn
localAuth.signOut = signOut
localAuth.getSession = getSession;
localAuth.signIn = signIn;
localAuth.signOut = signOut;

const state = useAuthState()
const state = useAuthState();

return {
...localAuth,
...state,
refresh,
}
}
refresh
};
};
Loading

0 comments on commit fbc6fc5

Please sign in to comment.