Files
sso-portal/components/login/util.ts
zhangheng 37f10118d3 chore: initial commit of sso-mock SSO login service
Next.js + Mantine mock SSO service providing account/password, phone
verify-code, and Feishu QR login. Issues a one-time sso_code that the
external app exchanges for user info.

Includes:
- Feishu callback fix: guard against duplicate/concurrent /api/login
  submissions of the same single-use authorization code (feishuLogin
  once-guard + memoized onLoginSuccess to stop effect re-fire).
- ADAPTATION_GUIDE.md: how to switch from the in-memory code store to
  the real backend auth/index + inner_get_user_info endpoints.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:09:30 +08:00

101 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import CryptoJS from 'crypto-js'
const CODE_CRYPTO_KEY = 'k#8Mp$2Qr&9Tz@4W'
export const APP_ID = 'cli_a56577acb2f3d00c' // 飞书应用 ID与 uirefbase 相同)
export const TENANT = 'coowa'
export const validatePhoneNumber = (str: string) => {
const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/
return reg.test(str)
}
/** 获取 url 中的某个参数值 */
export const getQueryVariable = (variable: string) => {
if (typeof window === 'undefined') return false
const query = window.location.search.substring(1)
const vars = query.split('&')
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split('=')
if (pair[0] === variable) {
return pair[1]
}
}
return false
}
/** 加载飞书二维码 SDK */
export function qr_load() {
return new Promise<void>((resolve, reject) => {
// 如果已加载则跳过
if ((window as any).QRLogin) {
resolve()
return
}
const script = document.createElement('script')
script.type = 'text/javascript'
script.src =
'https://sf3-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.2.js'
script.onerror = reject
script.onload = () => resolve()
document.head.appendChild(script)
})
}
/** 初始化飞书二维码 */
export async function qr_login(
appId: string,
redirect_url: string,
state: string
) {
const gotoUrl = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${appId}&redirect_uri=${encodeURIComponent(
redirect_url
)}&response_type=code&state=${encodeURIComponent(state)}`
const QRLogin = (window as any).QRLogin
const QRLoginObj = QRLogin({
id: 'login_container',
goto: gotoUrl,
style: `width: 270px;height: 270px;border: 0;border-radius:12px;
background-image:
radial-gradient(at 47% 33%, hsl(212.37, 72%, 59%) 0, transparent 59%),
radial-gradient(at 82% 65%, hsl(198.00, 100%, 50%) 0, transparent 55%);`,
})
const handleMessage = function (event: any) {
const origin = event.origin
if (
QRLoginObj.matchOrigin(origin) &&
window.location.href.indexOf('login') > -1
) {
const loginTmpCode = event.data
window.location.href = `${gotoUrl}&tmp_code=${loginTmpCode}`
}
}
if (typeof window.addEventListener !== 'undefined') {
window.addEventListener('message', handleMessage, false)
} else if (typeof (window as any).attachEvent !== 'undefined') {
;(window as any).attachEvent('onmessage', handleMessage)
}
}
/** 加密 — 与 uirefbase 客户端保持一致 */
export const encrypt = (data: string) => {
try {
return CryptoJS.AES.encrypt(data, CODE_CRYPTO_KEY).toString()
} catch (error) {
return data
}
}
/** 解密 */
export const decrypt = (data: string) => {
try {
const bytes = CryptoJS.AES.decrypt(data, CODE_CRYPTO_KEY)
return bytes.toString(CryptoJS.enc.Utf8)
} catch (error) {
return data
}
}