'use strict' import { ApiUrls, ApiUrlsV2, AUTH_STATE_CHANGED_TYPE, ErrorType, EVENTS, OAUTH_TYPE } from './consts' import { GetVerificationRequest, GetVerificationResponse, UserProfile, UserInfo, SignInRequest, SignUpRequest, VerifyRequest, VerifyResponse, GenProviderRedirectUriRequest, GenProviderRedirectUriResponse, GrantProviderTokenRequest, GrantProviderTokenResponse, PatchProviderTokenRequest, PatchProviderTokenResponse, SignInWithProviderRequest, SignInCustomRequest, BindWithProviderRequest, TransByProviderRequest, GrantTokenRequest, UnbindProviderRequest, CheckPasswordRequest, SetPasswordRequest, ChangeBoundProviderRequest, ChangeBoundProviderResponse, UpdatePasswordRequest, SudoResponse, SudoRequest, GetCustomSignTicketFn, QueryUserProfileRequest, QueryUserProfileResponse, ResetPasswordRequest, DeviceAuthorizeRequest, DeviceAuthorizeResponse, CheckUsernameRequest, CheckIfUserExistRequest, CheckIfUserExistResponse, WithSudoRequest, PublicKey, EncryptParams, ProviderSubType, GetMiniProgramQrCodeRequest, GetMiniProgramQrCodeStatusRequest, GetMiniProgramQrCodeResponse, GetMiniProgramQrCodeStatusResponse, ModifyUserBasicInfoRequest, EditContactRequest, AuthorizeInfoRequest, AuthorizeInfoResponse, AuthorizeDeviceRequest, AuthorizeRequest, AuthorizeResponse, GetUserBehaviorLog, GetUserBehaviorLogRes, RevokeDeviceRequest, SignoutResponse, ProvidersResponse, SignoutRequest, ModifyPasswordWithoutLoginRequest, } from './models' import { SimpleStorage, RequestFunction } from '../oauth2client/interface' import { OAuth2Client, defaultStorage } from '../oauth2client/oauth2client' import { Credentials } from '../oauth2client/models' import { Captcha, CaptchaOptions } from '../captcha/captcha' import { deepClone } from '../utils' import MyURLSearchParams from '../utils/urlSearchParams' import { SDKAdapterInterface } from '@cloudbase/adapter-interface' import { ICloudbaseConfig } from '@cloudbase/types' import { AuthError } from './auth-error' async function getEncryptUtils(isEncrypt, adapter: SDKAdapterInterface) { const getUtils = async () => { try { /* eslint-disable */ // @ts-ignore const utils = require('../utils/encrypt') /* eslint-enable */ return utils } catch (error) { try { // @ts-ignore const utils = await import('../utils/encrypt') return utils } catch (error) { return } } } try { if ((adapter?.root?.globalThis || globalThis).IS_MP_BUILD) { return } if (!process.env.IS_MP_BUILD && isEncrypt) { return getUtils() } } catch (error) { if (isEncrypt) { return getUtils() } } } export interface AuthOptions { apiOrigin: string apiPath?: string clientId: string /** * basic auth */ clientSecret?: string credentialsClient?: OAuth2Client request?: RequestFunction baseRequest?: RequestFunction storage?: SimpleStorage anonymousSignInFunc?: (Credentials) => Promise captchaOptions?: Partial env?: string wxCloud?: any adapter?: SDKAdapterInterface onCredentialsError?: (data: { msg: string; eventType?: 'credentials_error' }) => void headers?: { [key: string]: string } i18n?: ICloudbaseConfig['i18n'] useWxCloud?: boolean eventBus?: any /** * Set to true if you want to automatically detect OAuth grants in the URL * and exchange the code for credentials. */ detectSessionInUrl?: boolean /** * Enable debug logging */ debug?: boolean auth?: ICloudbaseConfig['auth'] } /** * Auth */ export class Auth { private static parseParamsToSearch(params: any): string { Object.keys(params).forEach((key) => { if (!params[key]) { delete params[key] } }) const searchParams = new MyURLSearchParams(params as any) return searchParams.toString() } private config: AuthOptions private getCustomSignTicketFn?: GetCustomSignTicketFn /** * constructor * @param {AuthOptions} opts */ constructor(opts: AuthOptions) { let { request } = opts let oAuth2Client = opts.credentialsClient if (!oAuth2Client) { const initOptions = { apiOrigin: opts.apiOrigin, apiPath: opts.apiPath, clientId: opts.clientId, storage: opts.storage, env: opts.env, baseRequest: opts.baseRequest /* || opts.request */, // opts.request 废弃不用来表示 oauth rquest anonymousSignInFunc: opts.anonymousSignInFunc, wxCloud: opts.wxCloud, onCredentialsError: opts.onCredentialsError, headers: opts.headers || {}, i18n: opts.i18n, debug: opts.debug, } oAuth2Client = new OAuth2Client(initOptions) } if (!request) { const baseRequest = oAuth2Client.request.bind(oAuth2Client) const captcha = new Captcha({ env: opts.env, clientId: opts.clientId, request: baseRequest, storage: opts.storage, adapter: opts.adapter, oauthInstance: this, ...opts.captchaOptions, }) request = captcha.request.bind(captcha) } this.config = { env: opts.env, apiOrigin: opts.apiOrigin, apiPath: opts.apiPath, clientId: opts.clientId, request, credentialsClient: oAuth2Client, storage: opts.storage || defaultStorage, adapter: opts.adapter, } } /** * 根据版本标识,处理待请求的url和params * @param params * @param key * @returns */ public getParamsByVersion(params: any, key: string) { const paramsTemp = deepClone(params) const url = { v2: ApiUrlsV2 }[paramsTemp?.version]?.[key] || ApiUrls[key] if (paramsTemp) { delete paramsTemp.version } return { params: paramsTemp, url } } /** * Sign in. * @param {SignInRequest} params A SignInRequest Object. * @return {Promise} A Promise object. */ public async signIn(params: SignInRequest): Promise { const version = params.version || 'v1' const res = this.getParamsByVersion(params, 'AUTH_SIGN_IN_URL') if (res.params.query) { delete res.params.query } const body = await this.getEncryptParams(res.params) const credentials: Credentials = await this.config.request(res.url, { method: 'POST', body, }) await this.config.credentialsClient.setCredentials({ ...credentials, version, }) return Promise.resolve(credentials) } /** * Sign in Anonymously * @return {Promise} A Promise object. */ public async signInAnonymously( data: { provider_token?: string } = {}, useWxCloud = false, ): Promise { const credentials: Credentials = await this.config.request(ApiUrls.AUTH_SIGN_IN_ANONYMOUSLY_URL, { method: 'POST', body: data, useWxCloud, }) await this.config.credentialsClient.setCredentials(credentials) return Promise.resolve(credentials) } /** * Sign up. * @param {SignUpRequest} params A SignUpRequest Object. * @return {Promise} A Promise object. */ public async signUp(params: SignUpRequest): Promise { const data: Credentials = await this.config.request(ApiUrls.AUTH_SIGN_UP_URL, { method: 'POST', body: params, }) await this.config.credentialsClient.setCredentials(data) return Promise.resolve(data) } /** * Sign out. * @return {Object} A Promise object. */ public async signOut(params?: SignoutRequest): Promise { let resp: SignoutResponse = {} if (params) { try { resp = await this.config.request(ApiUrls.AUTH_SIGNOUT_URL, { method: 'POST', withCredentials: true, body: params, }) } catch (err) { if (err.error !== ErrorType.UNAUTHENTICATED) { console.log('sign_out_error', err) } } await this.config.credentialsClient.setCredentials() return resp } const accessToken: string = await this.config.credentialsClient.getAccessToken() const data = await this.config.request(ApiUrls.AUTH_REVOKE_URL, { method: 'POST', body: { token: accessToken, }, }) await this.config.credentialsClient.setCredentials() return Promise.resolve(data) } /** * Revoke All Devices * @return {Object} A Promise object. */ public async revokeAllDevices(): Promise { await this.config.request(ApiUrls.AUTH_REVOKE_ALL_URL, { method: 'DELETE', withCredentials: true, }) } /** * Revoke Device * @return {Object} A Promise object. */ public async revokeDevice(params: RevokeDeviceRequest): Promise { await this.config.request(ApiUrls.AUTHORIZED_DEVICES_DELETE_URL + params.device_id, { method: 'DELETE', withCredentials: true, }) } /** * Get the verification. * @param {GetVerificationRequest} params A GetVerificationRequest Object. * @return {Promise} A Promise object. */ public async getVerification( params: GetVerificationRequest, options?: { withCaptcha: boolean }, ): Promise { let withCredentials = false // 发送短信时,如果时给当前用户发,则需要带上鉴权信息 if (params.target === 'CUR_USER') { withCredentials = true } else { const hasLogin = await this.hasLoginState() if (hasLogin) { withCredentials = true } } const body = deepClone(params) if (body.phone_number && !/^\+\d{1,3}\s+\d+/.test(body.phone_number)) { body.phone_number = `+86 ${body.phone_number}` } return this.config.request(ApiUrls.VERIFICATION_URL, { method: 'POST', body, withCaptcha: options?.withCaptcha || false, withCredentials, }) } /** * Verify the code * @param {VerifyRequest} params A VerifyRequest Object. * @return {Promise} A Promise object. */ public async verify(params: VerifyRequest): Promise { const res = this.getParamsByVersion(params, 'VERIFY_URL') const data = await this.config.request(res.url, { method: 'POST', body: res.params, }) if (params?.version === 'v2') { await this.config.credentialsClient.setCredentials({ ...data, version: 'v2', }) } return data } /** * Gen provider redirect uri. * @param {GenProviderRedirectUriRequest} params A GenProviderRedirectUriRequest object. * @return {Promise} A Promise object. */ public async genProviderRedirectUri(params: GenProviderRedirectUriRequest): Promise { // eslint-disable-next-line @typescript-eslint/naming-convention const { provider_redirect_uri: redirect_uri, other_params: otherParams = {}, ...restParams } = params if (redirect_uri && !restParams.redirect_uri) { restParams.redirect_uri = redirect_uri } let url = `${ApiUrls.PROVIDER_URI_URL}?${Auth.parseParamsToSearch(restParams)}` Object.keys(otherParams).forEach((key) => { const value = otherParams[key] if (key === 'sign_out_uri' && !(value?.length > 0)) { return } url += `&other_params[${key}]=${encodeURIComponent(value)}` }) return this.config.request(url, { method: 'GET', }) } /** * Grant provider token. * @param {GrantProviderTokenRequest} params A GrantProviderTokenRequest object. * @return {Promise} A Promise object. */ public async grantProviderToken( params: GrantProviderTokenRequest, useWxCloud = false, ): Promise { return this.config.request(ApiUrls.PROVIDER_TOKEN_URL, { method: 'POST', body: params, useWxCloud, }) } /** * Grant provider token. * @param {PatchProviderTokenRequest} params A PatchProviderTokenRequest object. * @return {Promise} A Promise object. */ public async patchProviderToken(params: PatchProviderTokenRequest): Promise { return this.config.request(ApiUrls.PROVIDER_TOKEN_URL, { method: 'PATCH', body: params, }) } /** * Signin with provider request. * @param {SignInWithProviderRequest} params A SignInWithProviderRequest object. * @return {Promise} A Promise object. */ public async signInWithProvider(params: SignInWithProviderRequest, useWxCloud = false): Promise { const res = this.getParamsByVersion(params, 'AUTH_SIGN_IN_WITH_PROVIDER_URL') const credentials: Credentials = await this.config.request(res.url, { method: 'POST', body: res.params, useWxCloud, }) await this.config.credentialsClient.setCredentials({ ...credentials, version: params?.version || 'v1', }) return Promise.resolve(credentials) } public async toBindIdentity(params: { provider_token: string provider: string credentials?: Credentials fireEvent?: boolean }) { const credentials = params.credentials || (await this.config.credentialsClient.localCredentials.getCredentials()) let res: any try { await this.bindWithProvider( { provider_token: params.provider_token, }, credentials, ) res = { data: { type: OAUTH_TYPE.BIND_IDENTITY, provider: params.provider }, error: null } } catch (error) { res = { data: { type: OAUTH_TYPE.BIND_IDENTITY }, error: new AuthError(error) } } if (params.fireEvent) { this.config.eventBus?.fire?.(EVENTS.AUTH_STATE_CHANGED, { event: AUTH_STATE_CHANGED_TYPE.BIND_IDENTITY, info: res, }) } return res } /** * 获取初始 session(如从 URL 中的 OAuth 回调)。 * 用于 OAuth2Client.initialize() 回调。 * 内部处理 URL 检测、OAuth 验证,返回 credentials 和 user。 * 不调用 setCredentials,由 OAuth2Client 负责保存。 * * @returns { data: { session: Credentials; user?: any } | null, error: Error | null } */ public async getInitialSession(): Promise<{ data: { session: Credentials; user?: any } | null error: Error | null }> { let data: any = {} try { // Check if running in browser if (typeof window === 'undefined' || typeof document === 'undefined') { return { data: null, error: null } } // Parse URL parameters const localSearch = new URLSearchParams(location?.search) const code = localSearch.get('code') const state = localSearch.get('state') // No OAuth callback detected if (!code || !state) { return { data: null, error: null } } // Get provider from sessionStorage (saved during signInWithOAuth) let cacheData: { provider?: string search?: string hash?: string type?: (typeof OAUTH_TYPE)[keyof typeof OAUTH_TYPE] } | null = null try { cacheData = JSON.parse(sessionStorage.getItem(state) || 'null') } catch { // ignore } data = { type: cacheData?.type } // Check for error in URL const errorParam = localSearch.get('error') const errorDescription = localSearch.get('error_description') if (errorParam || errorDescription) { return { data, error: new AuthError({ message: errorDescription || errorParam || 'Unknown error from OAuth provider' }), } } const provider = cacheData?.provider || localSearch.get('provider') if (!provider) { return { data, error: new AuthError({ message: 'Provider is required for OAuth verification' }) } } // 获取当前页面的 redirect_uri const redirectUri = location.origin + location.pathname // Step 1: 获取 provider token const { provider_token: providerToken } = await this.grantProviderToken({ provider_id: provider, provider_redirect_uri: redirectUri, provider_code: code, }) let credentials: Credentials let user: any = null let res: any if (cacheData.type === OAUTH_TYPE.BIND_IDENTITY) { credentials = await this.config.credentialsClient.localCredentials.getCredentials() res = await this.toBindIdentity({ provider, provider_token: providerToken, credentials }) } else if (cacheData.type === OAUTH_TYPE.SIGN_IN) { res = this.getParamsByVersion({ provider }, 'AUTH_SIGN_IN_WITH_PROVIDER_URL') // Step 2: 用 provider token 换取 credentials(直接调用 API,不触发 setCredentials) credentials = await this.config.request(res.url, { method: 'POST', body: { provider_token: providerToken }, }) // Step 3: 获取 user info,传入 getCredentials 函数避免死锁 // 不调用 setCredentials,由 OAuth2Client._initialize 负责保存 try { user = await this.getUserInfo({ credentials }) } catch (e) { console.error('get user info error', e) // 获取 user 失败不影响登录流程 } res = { data: { ...data, session: credentials, user }, error: null } } // Clean up URL parameters and restore original URL state localSearch.delete('code') localSearch.delete('state') localSearch.delete('provider') this.restoreUrlState( cacheData?.search === undefined ? `?${localSearch.toString()}` : cacheData.search, cacheData?.hash || location.hash, ) // Remove session storage try { sessionStorage.removeItem(state) } catch { // ignore } return res } catch (error) { return { data, error: new AuthError(error) } } } /** * Signin with custom. * @param {SignInCustomRequest} params A SignInCustomRequest object. * @return {Promise} A Promise object. */ public async signInCustom(params: SignInCustomRequest): Promise { const credentials: Credentials = await this.config.request(ApiUrls.AUTH_SIGN_IN_CUSTOM, { method: 'POST', body: params, }) await this.config.credentialsClient.setCredentials(credentials) return Promise.resolve(credentials) } /** * sign in with wechat idendify * 需要结合 http overcallFunction 使用 */ public async signInWithWechat(params: any = {}): Promise { const credentials: Credentials = await this.config.request(ApiUrls.AUTH_SIGN_IN_WITH_WECHAT_URL, { method: 'POST', body: params, }) await this.config.credentialsClient.setCredentials(credentials) return Promise.resolve(credentials) } /** * Bind with provider * @param {BindWithProviderRequest} params A BindWithProviderRequest object. * @return {Promise} A Promise object. */ public async bindWithProvider(params: BindWithProviderRequest, credentials?: Credentials): Promise { return this.config.request(ApiUrls.PROVIDER_BIND_URL, { method: 'POST', body: params, withCredentials: true, getCredentials: credentials ? () => credentials : undefined, }) } /** * Get the user profile. * @return {Promise} A Promise object. */ public async getUserProfile(params: { version?: string }): Promise { return this.getUserInfo(params) } /** * Get the user info. * @param params.getCredentials Optional custom getCredentials function to bypass default getCredentials() and avoid deadlock * @return {Promise} A Promise object. */ public async getUserInfo(params: { version?: string query?: string credentials?: Credentials } = {},): Promise { const res = this.getParamsByVersion(params, 'USER_ME_URL') if (res.params?.query) { const searchParams = new MyURLSearchParams(res.params.query as any) res.url += `?${searchParams.toString()}` } const userInfo = await this.config.request(res.url, { method: 'GET', withCredentials: true, getCredentials: params.credentials ? () => params.credentials : undefined, }) if (userInfo.sub) { userInfo.uid = userInfo.sub } return userInfo } /** * Get the user info by weda plugin. * @return {Promise} A Promise object. */ public async getWedaUserInfo(): Promise { const userInfo = await this.config.request(ApiUrls.WEDA_USER_URL, { method: 'GET', withCredentials: true, }) return userInfo } /** * Delete me * @param params */ public async deleteMe(params: WithSudoRequest): Promise { const res = this.getParamsByVersion(params, 'USER_ME_URL') const url = `${res.url}?${Auth.parseParamsToSearch(res.params)}` return this.config.request(url, { method: 'DELETE', withCredentials: true, }) } /** * hasLoginState check if has login state * @return {Promise} A Promise object. */ public async hasLoginState(): Promise { try { await this.config.credentialsClient.getAccessToken() return true } catch (error) { return false } } public hasLoginStateSync(): Credentials | null { const credentials = this.config.credentialsClient.getCredentialsSync() return credentials } public async getLoginState(): Promise { return this.config.credentialsClient.getCredentialsAsync() } /** * Trans by provider. * @param {TransByProviderRequest} params A TransByProviderRequest object. * @return {Promise} A Promise object. */ public async transByProvider(params: TransByProviderRequest): Promise { return this.config.request(ApiUrls.USER_TRANS_BY_PROVIDER_URL, { method: 'PATCH', body: params, withCredentials: true, }) } /** * Grant token. * @param {GrantTokenRequest} params A GrantTokenRequest object. * @return {Promise} A Promise object. */ public async grantToken(params: GrantTokenRequest): Promise { const res = this.getParamsByVersion(params, 'AUTH_TOKEN_URL') return this.config.request(res.url, { method: 'POST', body: res.params, }) } /** * Get the provide list. * @return {Promise} A Promise object. */ public async getProviders(): Promise { return this.config.request(ApiUrls.PROVIDER_LIST, { method: 'GET', withCredentials: true, }) } /** * unbind provider. * @param {UnbindProviderRequest} params * @return {Promise} */ public async unbindProvider(params: UnbindProviderRequest): Promise { return this.config.request(`${ApiUrls.PROVIDER_UNBIND_URL}/${params.provider_id}`, { method: 'DELETE', withCredentials: true, }) } /** * check Password. * @param {CheckPasswordRequest} params * @return {Promise} */ public async checkPassword(params: CheckPasswordRequest): Promise { return this.config.request(`${ApiUrls.CHECK_PWD_URL}`, { method: 'POST', withCredentials: true, body: params, }) } /** * Edit Contact 修改 手机号 或 邮箱 * @param {EditContactRequest} params * @return {Promise} */ public async editContact(params: EditContactRequest): Promise { return this.config.request(`${ApiUrls.BIND_CONTACT_URL}`, { method: 'PATCH', withCredentials: true, body: params, }) } /** * Set Password. * @param {SetPasswordrRequest} params * @return {Promise} */ public async setPassword(params: SetPasswordRequest): Promise { return this.config.request(`${ApiUrls.AUTH_SET_PASSWORD}`, { method: 'PATCH', withCredentials: true, body: params, }) } /** * updatePasswordByOld 使用旧密码修改密码,如果已经绑定手机号,请先:sudo,再修改密码 * @param {SetPasswordrRequest} params * @return {Promise} */ public async updatePasswordByOld(params: UpdatePasswordRequest): Promise { const sudoToken = await this.sudo({ password: params.old_password }) return this.setPassword({ sudo_token: sudoToken.sudo_token, new_password: params.new_password, }) } /** * sudo * @param {sudo} params * @return {Promise} */ public async sudo(params: SudoRequest): Promise { return this.config.request(`${ApiUrls.SUDO_URL}`, { method: 'POST', withCredentials: true, body: params, }) } /** * Get the current user verification. * @param {GetVerificationRequest} params A GetVerificationRequest Object. * @return {Promise} A Promise object. */ public async sendVerificationCodeToCurrentUser(params: GetVerificationRequest): Promise { params.target = 'CUR_USER' return this.config.request(ApiUrls.VERIFICATION_URL, { method: 'POST', body: params, withCredentials: true, withCaptcha: true, }) } /** * change Bound provider. * @param {ChangeBoundProviderRequest} params A GetVerificationRequest Object. * @return {Promise} A Promise object. */ public async changeBoundProvider(params: ChangeBoundProviderRequest): Promise { return this.config.request(`${ApiUrls.PROVIDER_LIST}/${params.provider_id}/trans`, { method: 'POST', body: { provider_trans_token: params.trans_token, }, withCredentials: true, }) } /** * Patch the user profile. 没有和数据源同步 * @deprecated use updateUserBasicInfo * @param {UserProfile} params A UserProfile Object. * @return {Promise} A Promise object. */ public async setUserProfile(params: UserProfile): Promise { return this.config.request(ApiUrls.USER_PRIFILE_URL, { method: 'PATCH', body: params, withCredentials: true, }) } /** * Update user basic info * @return {Promise} */ public async updateUserBasicInfo(params: ModifyUserBasicInfoRequest): Promise { return this.config.request(ApiUrls.USER_BASIC_EDIT_URL, { method: 'POST', withCredentials: true, body: params, }) } /** * Patch the user profile. * @param {QueryUserProfileReq} appended_params A QueryUserProfileReq Object. * @return {Promise} A Promise object. */ public async queryUserProfile(params: QueryUserProfileRequest): Promise { // let url = new URL(ApiUrls.USER_QUERY_URL); const searchParams = new MyURLSearchParams(params as any) // url.search = searchParams.toString(); return this.config.request(`${ApiUrls.USER_QUERY_URL}?${searchParams.toString()}`, { method: 'GET', withCredentials: true, }) } /** * setCustomSignFunc set the get ticket function * @param getTickFn */ public setCustomSignFunc(getTickFn: GetCustomSignTicketFn) { this.getCustomSignTicketFn = getTickFn } /** * SignInWithCustomTicket custom signIn * @constructor */ // public async signInWithCustomTicket(params?: { version?: string }): Promise { // const customSignTicketFn = this.getCustomSignTicketFn // if (!customSignTicketFn) { // return Promise.reject({ // }) // } // const customTicket = await customSignTicketFn() // return this.signInWithProvider({ // ...params, // provider_id: 'custom', // provider_token: customTicket, // }) // } public async signInWithCustomTicket(): Promise { const customSignTicketFn = this.getCustomSignTicketFn if (!customSignTicketFn) { return Promise.reject({ error: 'failed_precondition', error_description: 'please use setCustomSignFunc to set custom sign function', }) } const customTicket = await customSignTicketFn() return this.signInCustom({ provider_id: 'custom', ticket: customTicket, }) } /** * Reset password * @param {ResetPasswordRequest} params * @returns {Promise} * @memberof Auth */ public async resetPassword(params: ResetPasswordRequest): Promise { return this.config.request(ApiUrls.AUTH_RESET_PASSWORD, { method: 'POST', body: params, // withCredentials: true }) } /** * Authorize oauth Authorize * @param params * @constructor */ public async authorize(params: AuthorizeRequest): Promise { return this.config.request(ApiUrls.AUTHORIZE_URL, { method: 'POST', withCredentials: true, body: params, }) } /** * authorize device * @param params */ public async authorizeDevice(params: AuthorizeDeviceRequest): Promise { return this.config.request(ApiUrls.AUTHORIZE_DEVICE_URL, { method: 'POST', withCredentials: true, body: params, }) } /** * device authorization * @param {DeviceAuthorizeRequest} params * @returns {Promise} * @memberof Auth */ public async deviceAuthorize(params: DeviceAuthorizeRequest): Promise { return this.config.request(ApiUrls.AUTH_GET_DEVICE_CODE, { method: 'POST', body: params, withCredentials: true, }) } /** * OAuth get authorize info * @param params * @constructor */ public async authorizeInfo(params: AuthorizeInfoRequest): Promise { const url = `${ApiUrls.AUTHORIZE_INFO_URL}?${Auth.parseParamsToSearch(params)}` let withBasicAuth = true let withCredentials = false const hasLogin = await this.hasLoginState() if (hasLogin) { withCredentials = true withBasicAuth = false } return this.config.request(url, { method: 'GET', withBasicAuth, withCredentials, }) } public async checkUsername(params: CheckUsernameRequest): Promise { return this.config.request(ApiUrls.CHECK_USERNAME, { method: 'GET', body: params, withCredentials: true, }) } public async checkIfUserExist(params: CheckIfUserExistRequest): Promise { const searchParams = new MyURLSearchParams(params as any) return this.config.request(`${ApiUrls.CHECK_IF_USER_EXIST}?${searchParams.toString()}`, { method: 'GET', }) } public async loginScope(): Promise { return this.config.credentialsClient.getScope() } public async loginGroups(): Promise { return this.config.credentialsClient.getGroups() } public async refreshTokenForce(params: { version?: string }) { const credentials: Credentials = await this.config.credentialsClient.getCredentials() return await this.config.credentialsClient.refreshToken({ ...credentials, version: params?.version || 'v1', }) } public async getCredentials() { return this.config.credentialsClient.getCredentials() } /** * get public key for request params encryption * @returns */ public async getPublicKey(): Promise { return this.config.request(ApiUrlsV2.AUTH_PUBLIC_KEY, { method: 'POST', body: {}, }) } /** * encrypt request params * @param params * @returns */ public async getEncryptParams(params: Record): Promise { const { isEncrypt } = params delete params.isEncrypt const payload = deepClone(params) const encryptUtils = await getEncryptUtils(isEncrypt, this.config.adapter) if (!encryptUtils) { return params } let publicKey = '' // eslint-disable-next-line @typescript-eslint/naming-convention let public_key_thumbprint = '' try { const res = await this.getPublicKey() publicKey = res.public_key public_key_thumbprint = res.public_key_thumbprint if (!publicKey || !public_key_thumbprint) { throw res } } catch (error) { throw error } return { params: encryptUtils.getEncryptInfo({ publicKey, payload }), public_key_thumbprint, } } /** * get provider sub type * @returns */ public async getProviderSubType(): Promise { return this.config.request(ApiUrls.GET_PROVIDER_TYPE, { method: 'POST', body: { provider_id: 'weda', }, }) } /** * get provider sub type * @returns */ public async verifyCaptchaData({ token, key }: { token: string; key: string }) { return this.config.request<{ captcha_token: string; expires_in: number }>(ApiUrls.VERIFY_CAPTCHA_DATA_URL, { method: 'POST', body: { token, key }, withCredentials: false, }) } public async createCaptchaData({ state, redirect_uri = undefined }) { return this.config.request<{ token: string; data: string }>(ApiUrls.CAPTCHA_DATA_URL, { method: 'POST', body: { state, redirect_uri }, withCredentials: false, }) } /** * mini-program scan code * @returns */ public async getMiniProgramCode(params: GetMiniProgramQrCodeRequest): Promise { return this.config.request(ApiUrls.GET_MINIPROGRAM_QRCODE, { method: 'POST', body: params, }) } /** * mini-program scan code status * @returns */ public async getMiniProgramQrCodeStatus(params: GetMiniProgramQrCodeStatusRequest,): Promise { return this.config.request(ApiUrls.GET_MINIPROGRAM_QRCODE_STATUS, { method: 'POST', body: params, }) } /** * get user behavior log * @param params */ public async getUserBehaviorLog(params: GetUserBehaviorLog): Promise { const action = { LOGIN: 'query[action]=USER_LOGIN', MODIFY: 'ne_query[action]=USER_LOGIN' } const url = `${ApiUrls.GET_USER_BEHAVIOR_LOG}?${action[params.type]}&limit=${params.limit}${ params.page_token ? `&page_token=${params.page_token}` : '' }` return this.config.request(url, { method: 'GET', withCredentials: true, }) } /** * 这个方法是用户自己修改自己的密码,不同于/auth/v1/user/password接口,该接口是管理员修改个人的 * @param {ModifyUserBasicInfoRequest} params A ModifyUserBasicInfoRequest Object. * @return {Promise} A Promise object. */ public async modifyPassword(params: ModifyUserBasicInfoRequest): Promise { let publicKey = '' // eslint-disable-next-line @typescript-eslint/naming-convention let public_key_thumbprint = '' const encryptUtils = await getEncryptUtils(true, this.config.adapter) if (!encryptUtils) { throw new Error('do not support encrypt, a encrypt util required.') } try { const res = await this.getPublicKey() publicKey = res.public_key public_key_thumbprint = res.public_key_thumbprint if (!publicKey || !public_key_thumbprint) { throw res } } catch (error) { throw error } // eslint-disable-next-line @typescript-eslint/naming-convention const encrypt_password = params.password ? encryptUtils.getEncryptInfo({ publicKey, payload: params.password }) : '' // eslint-disable-next-line @typescript-eslint/naming-convention const encrypt_new_password = encryptUtils.getEncryptInfo({ publicKey, payload: params.new_password }) return this.config.request(ApiUrls.USER_BASIC_EDIT_URL, { method: 'POST', withCredentials: true, body: { user_id: params.user_id, encrypt_password, encrypt_new_password, public_key_thumbprint, }, }) } /** * 重置密码,该接口无需登录就可以重置密码 * @param {ModifyPasswordWithoutLoginRequest} params A ModifyPasswordWithoutLoginRequest Object. * @return {Promise} A Promise object. */ public async modifyPasswordWithoutLogin(params: ModifyPasswordWithoutLoginRequest): Promise { let publicKey = '' // eslint-disable-next-line @typescript-eslint/naming-convention let public_key_thumbprint = '' const encryptUtils = await getEncryptUtils(true, this.config.adapter) if (!encryptUtils) { throw new Error('do not support encrypt, a encrypt util required.') } try { const res = await this.getPublicKey() publicKey = res.public_key public_key_thumbprint = res.public_key_thumbprint if (!publicKey || !public_key_thumbprint) { throw res } } catch (error) { throw error } // eslint-disable-next-line @typescript-eslint/naming-convention const encrypt_password = params.password ? encryptUtils.getEncryptInfo({ publicKey, payload: params.password }) : '' // eslint-disable-next-line @typescript-eslint/naming-convention const encrypt_new_password = encryptUtils.getEncryptInfo({ publicKey, payload: params.new_password }) return this.config.request(ApiUrlsV2.AUTH_RESET_PASSWORD, { method: 'POST', body: { username: params.username, password: encrypt_password, new_password: encrypt_new_password, public_key_thumbprint, }, }) } /** * Restore URL state after OAuth callback */ private restoreUrlState(search?: string, hash?: string): void { if (search === undefined) return try { const url = new URL(window.location.href) url.search = search url.hash = hash || '' window.history.replaceState(null, '', url.toString()) window.location.replace(url.toString()) } catch { // ignore } } }