auth.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. import { validateCredentials } from './validate-credentials.js'
  2. import { buildEncryptedLoginBody } from './login-rsa.js'
  3. import { get, post } from './request.js'
  4. import {
  5. getToken,
  6. getRefreshToken,
  7. isLoggedIn,
  8. getUser,
  9. clearSession,
  10. saveSession,
  11. updateStoredUser,
  12. forceRelogin,
  13. setReturnUrl,
  14. takeReturnUrl
  15. } from './auth-session.js'
  16. export { validateCredentials }
  17. export {
  18. getToken,
  19. getRefreshToken,
  20. isLoggedIn,
  21. getUser,
  22. clearSession,
  23. updateStoredUser,
  24. forceRelogin,
  25. setReturnUrl,
  26. takeReturnUrl
  27. }
  28. /**
  29. * 拉取当前用户资料并写回本地(含职位、部门)。
  30. * @returns {Promise<object|null>}
  31. */
  32. export async function fetchCurrentUser() {
  33. const token = getToken()
  34. if (!token) return null
  35. const data = await get('/web/auth/current-user', {
  36. Authorization: 'Bearer ' + token
  37. })
  38. if (data) {
  39. const prev = getUser() || {}
  40. const merged = Object.assign({}, prev, data)
  41. updateStoredUser(merged)
  42. return merged
  43. }
  44. return null
  45. }
  46. /**
  47. * 可选:清除历史 mock_ 前缀假会话,避免误判已登录。
  48. */
  49. export function clearLegacyMockSessionIfNeeded() {
  50. const token = getToken()
  51. if (typeof token === 'string' && token.indexOf('mock_') === 0) {
  52. clearSession()
  53. }
  54. }
  55. /**
  56. * 真实账号密码登录。
  57. * @param {{ username: string, password: string }} params
  58. */
  59. export async function passwordLogin({ username, password }) {
  60. const v = validateCredentials(username, password)
  61. if (!v.ok) {
  62. const err = new Error(v.message)
  63. err.code = v.code
  64. throw err
  65. }
  66. const body = await buildEncryptedLoginBody(
  67. (username || '').trim(),
  68. password || ''
  69. )
  70. const data = await post('/web/auth/login', body)
  71. if (!data || !data.token) {
  72. throw new Error('登录失败')
  73. }
  74. saveSession({
  75. token: data.token,
  76. refreshToken: data.refreshToken,
  77. userInfo: data.userInfo
  78. })
  79. // 密码登录成功后静默绑定微信(失败不影响登录)
  80. bindWechatQuietly()
  81. return data
  82. }
  83. /**
  84. * 微信授权登录(openid 优先;未绑定需 phoneCode)。
  85. */
  86. export async function wechatLogin({ loginCode, phoneCode }) {
  87. const payload = { loginCode }
  88. if (phoneCode) {
  89. payload.phoneCode = phoneCode
  90. }
  91. const data = await post('/mini/auth/wechat-login', payload)
  92. if (!data || !data.token) {
  93. throw new Error('登录失败')
  94. }
  95. saveSession({
  96. token: data.token,
  97. refreshToken: data.refreshToken,
  98. userInfo: data.userInfo
  99. })
  100. return data
  101. }
  102. /**
  103. * 网页扫码登录:标记会话已扫码(供 PC 端轮询展示「已扫码」)。
  104. * @param {string} sessionId 完整 sessionId 或小程序码 scene(短码)
  105. */
  106. export async function webQrScanned(sessionId) {
  107. return post('/mini/auth/web-qr-scanned', { sessionId })
  108. }
  109. /**
  110. * 网页扫码登录:授权手机号并确认(不写本地会话,仅完成 PC 端登录)。
  111. * @param {{ sessionId: string, loginCode: string, phoneCode: string }} params
  112. */
  113. export async function webQrConfirm({ sessionId, loginCode, phoneCode }) {
  114. return post('/mini/auth/web-qr-confirm', {
  115. sessionId,
  116. loginCode,
  117. phoneCode
  118. })
  119. }
  120. /**
  121. * 深链无 JWT 时静默换票(仅 loginCode,后端按已绑 openid 签发)。
  122. */
  123. export async function silentWechatLogin() {
  124. const loginRes = await new Promise((resolve, reject) => {
  125. uni.login({ provider: 'weixin', success: resolve, fail: reject })
  126. })
  127. const loginCode = loginRes && loginRes.code
  128. if (!loginCode) throw new Error('静默登录失败')
  129. const data = await post('/mini/auth/wechat-login', { loginCode })
  130. if (!data || !data.token) throw new Error('静默登录失败')
  131. saveSession({
  132. token: data.token,
  133. refreshToken: data.refreshToken,
  134. userInfo: data.userInfo
  135. })
  136. return data
  137. }
  138. /**
  139. * 已登录态下用 wx.login code 绑定 openid;失败静默。
  140. */
  141. export async function bindWechatQuietly() {
  142. try {
  143. const loginRes = await new Promise((resolve, reject) => {
  144. uni.login({ provider: 'weixin', success: resolve, fail: reject })
  145. })
  146. const loginCode = loginRes && loginRes.code
  147. if (!loginCode) return
  148. const token = getToken()
  149. if (!token) return
  150. await post(
  151. '/mini/auth/wechat-bind',
  152. { loginCode },
  153. { Authorization: 'Bearer ' + token }
  154. )
  155. } catch (e) {
  156. console.warn('wechat bind skipped', e && e.message)
  157. }
  158. }
  159. /**
  160. * 查询当前用户是否已绑定服务号(用于微信审批通知)。
  161. * @returns {Promise<{ bound: boolean }>}
  162. */
  163. export async function fetchMpBindStatus() {
  164. const token = getToken()
  165. if (!token) return { bound: false }
  166. const data = await get('/mini/auth/mp-bind-status', {
  167. Authorization: 'Bearer ' + token
  168. })
  169. return { bound: !!(data && data.bound) }
  170. }