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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | 2x 2x 4x 4x 4x 4x 4x 4x 4x 12x 3x 4x 1x 5x 5x 5x 5x 5x 5x 3x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 8x 8x 8x 8x 5x 5x 5x 5x 1x 4x 4x 1x 3x 1x 2x 2x 2x 1x 1x 2x 5x 5x 1x 4x 4x 4x 4x 4x 3x 3x 3x 1x 3x 3x 3x 3x 2x 1x | // sso sdk
import aes from 'crypto-js/aes'
import utf8 from 'crypto-js/enc-utf8'
import config from './config'
import { Envs } from './type'
export { Envs } from './type'
export interface ICstProps {
env: Envs
source: string
source_appid?: string
}
export interface ISignInProps {
ref?: string
}
export enum AuthType {
WxH5 = 'wx_h5',
Pc = 'pc',
}
export class Auth {
private readonly SSO_HOST_MAP: Map<string, string>
public readonly SSO_HOST: string
private readonly cstProps: ICstProps & { source_appid: string }
static readonly EXPIRE_TIME = config.EXPIRE_TIME
static readonly DEFAULT_SOURCE_APPID_MAP = config.DEFAULT_SOURCE_APPID_MAP
constructor(params: ICstProps) {
const defalutSourceAppid = config.DEFAULT_SOURCE_APPID_MAP.get(params.env)!
this.cstProps = {
...params,
source_appid: params.source_appid || defalutSourceAppid,
}
this.SSO_HOST_MAP = config.SSO_HOST_MAP
this.SSO_HOST = this.SSO_HOST_MAP.get(params.env)!
this.checkEnvs()
}
private checkEnvs() {
let status = false
for (const key in Envs) {
if (Envs[key as keyof typeof Envs] === this.cstProps.env) {
status = true
}
}
if (!status) {
throw new Error(`无效 env:${this.cstProps.env}`)
}
}
private getSignInUrlIst(params: ISignInProps & { auth_type: AuthType }) {
const signInUrlIst = new URL(this.SSO_HOST)
signInUrlIst.pathname = '/sign_in'
signInUrlIst.searchParams.set(
'ref',
decodeURIComponent(params.ref || location.href),
)
signInUrlIst.searchParams.set('auth_type', params.auth_type)
signInUrlIst.searchParams.set('source', this.cstProps.source)
return signInUrlIst
}
/**
* wx h5登录
* @param params.ref 登录成功后的回调url
*/
public toSignInWxH5(params: ISignInProps = {}) {
const signInUrlIst = this.getSignInUrlIst({
...params,
auth_type: AuthType.WxH5,
})
signInUrlIst.searchParams.set('source_appid', this.cstProps.source_appid)
const url = signInUrlIst.href
location.replace(url)
}
/**
* pc登录
* @param params.ref 登录成功后的回调url
*/
public toSignInPC(params: ISignInProps = {}) {
const signInUrlIst = this.getSignInUrlIst({
...params,
auth_type: AuthType.Pc,
})
const url = signInUrlIst.href
location.replace(url)
}
/**
* 退出登录
* @param ref 回调链接(可选,默认为当前链接)
*/
public signOut({ ref = location.href }: ISignInProps = {}) {
const refUrl = decodeURIComponent(ref)
localStorage.removeItem('token')
const signOutUrlIst = new URL(this.SSO_HOST)
signOutUrlIst.pathname = 'sign_out'
signOutUrlIst.searchParams.set('ref', refUrl)
const url = signOutUrlIst.href
location.replace(url)
}
/**
* 获取加密后的token
* params.token 需要加密的token
*/
static getEncodeToken(
params: {
token?: string
} = {},
) {
const token = params.token || localStorage.getItem('token') || ''
const tokenInfo = { token, expire: Date.now() + config.EXPIRE_TIME }
const encodeToken = aes
.encrypt(JSON.stringify(tokenInfo), config.SECRET)
.toString()
// base64 避免某些环境链接上的+取下来会变成空格
return window.btoa(encodeToken)
}
/**
* 从地址栏上解出token,并存入本地,此方法可以在全局挂上
* params.currentUrl? 需要解析的地址,默认为当前地址
* params.isKeepTokenInUrl? 是否需要保留sso_token在url上,默认会去除掉
*/
static decodeSsoToken(
params: {
currentUrl?: string
isKeepTokenInUrl?: boolean
} = {},
) {
const currentUrl = params.currentUrl || location.href
const urlIst = new URL(currentUrl)
const ssoToken = urlIst.searchParams.get('sso_token')
if (!ssoToken) {
return { status: false, msg: '链接上不存在sso_token' }
}
// console.log(ssoToken, 444)
const tokenInfo = this.getTokenInfo(ssoToken)
if (!tokenInfo || typeof tokenInfo !== 'object') {
return { status: false, msg: 'token解密失败' }
}
if (Date.now() > tokenInfo.expire) {
return { status: false, msg: 'sso_token已过期' }
}
localStorage.setItem('token', tokenInfo.token)
urlIst.searchParams.delete('sso_token')
// location.replace(urlIst.href)
if (!params.isKeepTokenInUrl) {
history.replaceState(
null,
'',
`${urlIst.pathname}${location.search}${location.hash}`,
)
} else {
console.log('keep token')
}
return { status: true, msg: '' }
}
/**
* 解析 token
*/
static getTokenInfo(ticket: string) {
let decodeTicket
try {
decodeTicket = window.atob(decodeURIComponent(ticket))
} catch (err) {
return null
}
const tokenInfoStr = aes.decrypt(decodeTicket, config.SECRET).toString(utf8)
// console.log(tokenInfoStr, decodeTicket, 111)
let tokenInfo = null
try {
tokenInfo = JSON.parse(tokenInfoStr)
} catch {
/* istanbul ignore next */
console.error('JSON parse error')
}
return tokenInfo as { token: string; expire: number } | string | null
}
/**
* 把token ticket 带在一个链接上,然后跳过去
* @param params.url 跳过去的链接
* @param params?.token 需要加密的token,不填默认取本地token
* @param params?.isReplace 跳过去是否replace当前页面,默认location.assign
*/
static toUrlWithTicket(params: {
url: string
token?: string
isReplace?: boolean
}) {
const urlIst = new URL(params.url)
const token = params.token || localStorage.getItem('token')
if (!token) {
alert('[sso] 本地凭证不存在')
// return
}
const ticket = this.getEncodeToken({ token: token ?? '' })
urlIst.searchParams.set('sso_token', ticket)
const url = urlIst.href
if (params.isReplace) {
location.replace(url)
} else {
location.assign(url)
}
}
}
|