All files / src/utils oidc.ts

0% Statements 0/15
0% Branches 0/12
0% Functions 0/9
0% Lines 0/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69                                                                                                                                         
import { UserManager, WebStorageStateStore } from 'oidc-client-ts'
 
/**
 * 内存存储:登录后的 User(含 access_token)只存于此,不写入 localStorage/sessionStorage。
 * 刷新页面或关闭标签即丢失,符合 D8「access_token 仅存内存」。
 * 实现标准 Web Storage 接口(含 length/key/clear),以满足 oidc-client-ts v3 的存储类型。
 */
class MemoryStore implements Storage {
  private readonly map = new Map<string, string>()
  get length(): number {
    return this.map.size
  }
  getItem(key: string): string | null {
    return this.map.has(key) ? this.map.get(key)! : null
  }
  setItem(key: string, value: string): void {
    this.map.set(key, value)
  }
  removeItem(key: string): void {
    this.map.delete(key)
  }
  clear(): void {
    this.map.clear()
  }
  key(index: number): string | null {
    return Array.from(this.map.keys())[index] ?? null
  }
}
 
const authority = import.meta.env.VITE_OIDC_AUTHORITY ?? 'https://auth.anjibao.cn'
const clientId = import.meta.env.VITE_OIDC_CLIENT_ID ?? '6db48077896a73e99c4a'
const scope = import.meta.env.VITE_OIDC_SCOPE ?? 'openid profile phone'
 
export const userManager = new UserManager({
  authority,
  client_id: clientId,
  redirect_uri: `${window.location.origin}/auth/callback`,
  post_logout_redirect_uri: `${window.location.origin}/logged-out`,
  response_type: 'code',
  scope,
  // 授权码流程跨页面跳转所需的临时 state(code_verifier/nonce),同标签页内保留
  stateStore: new WebStorageStateStore({ store: window.sessionStorage }),
  // 登录态(含 token)仅内存
  userStore: new WebStorageStateStore({ store: new MemoryStore() }),
  // 第一版不开隐藏 iframe 自动续期:过期/失效由 401 触发一次重新授权(同站 SSO 会话在则无感跳回)
  automaticSilentRenew: false,
  // 令牌足以解身份,无需额外再拉 userinfo
  loadUserInfo: false
})
 
/** 发起登录(整页跳到 IdP;在 iframe 内即 iframe 内跳转)。 */
export function signinRedirect(returnPath?: string): Promise<void> {
  return userManager.signinRedirect({ state: { returnPath } })
}
 
/** 处理授权码回调,交换并装载登录态,返回发起登录前的路径。 */
export async function handleRedirectCallback(): Promise<string> {
  const user = await userManager.signinRedirectCallback()
  return (user.state as { returnPath?: string } | undefined)?.returnPath ?? '/'
}
 
/**
 * 登出:走 OIDC RP-Initiated Logout,跳转 IdP 结束 SSO 会话后回到 /logged-out。
 * 转介人在 Casdoor 仅消费本应用,结束全局会话无「连带退出其他系统」副作用。
 */
export function signoutRedirect(): Promise<void> {
  return userManager.signoutRedirect()
}