UBERF-7753: Change auth approach for providers (#6234)

Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
Alexey Zinoviev 2024-08-06 10:50:55 +04:00 committed by GitHub
parent 5450a0754d
commit d2b2cbf984
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 106 additions and 77 deletions

View File

@ -1271,6 +1271,9 @@ dependencies:
'@types/qs':
specifier: ~6.9.7
version: 6.9.11
'@types/querystringify':
specifier: ^2.0.2
version: 2.0.2
'@types/request':
specifier: ~2.48.8
version: 2.48.12
@ -1694,6 +1697,9 @@ dependencies:
qs:
specifier: ~6.11.0
version: 6.11.2
querystringify:
specifier: ^2.2.0
version: 2.2.0
react:
specifier: ^18.2.0
version: 18.2.0
@ -9307,6 +9313,10 @@ packages:
resolution: {integrity: sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==}
dev: false
/@types/querystringify@2.0.2:
resolution: {integrity: sha512-7d6OQK6pJ//zE32XLK3vI6GHYhBDcYooaRco9cKFGNu59GVatL5+u7rkiAViq44DxDTd/7QQNBWSDHfJGBz/Pw==}
dev: false
/@types/range-parser@1.2.7:
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
dev: false
@ -23758,7 +23768,7 @@ packages:
dev: false
file:projects/account-service.tgz:
resolution: {integrity: sha512-m2wnBUxpiATGM4uSuNprCebldYmLYweixWs/lkbmn2oxzV797WaTrigIQEEPowYOydBskozS7uc/PgBj9WhkCw==, tarball: file:projects/account-service.tgz}
resolution: {integrity: sha512-ezhFLrsQ8CjpPUIvobegFaStE0c6PJyHqd4V8731uyNwVe+Vhm+Wx5Qoa/VOVqdCWOixjZdP9sBWILsRU9nKkA==, tarball: file:projects/account-service.tgz}
name: '@rush-temp/account-service'
version: 0.0.0
dependencies:
@ -24236,7 +24246,7 @@ packages:
dev: false
file:projects/auth-providers.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-ixfEzxmzLbrHghRymDcvdyz35q9DsDnA1qqheztJNa2zLH5102sfEMEooVLShU3cD+uf/KhNbcTyea1on8GlfA==, tarball: file:projects/auth-providers.tgz}
resolution: {integrity: sha512-5ii9QDKZ+v5awKhzsUOf7Y83PSCUzX6BKFZjY21w8oy+pJRZPTVbKYJhQC4i1AuvahXCObkMbCc3ljs0L+vgIw==, tarball: file:projects/auth-providers.tgz}
id: file:projects/auth-providers.tgz
name: '@rush-temp/auth-providers'
version: 0.0.0
@ -24249,6 +24259,7 @@ packages:
'@types/node-fetch': 2.6.11
'@types/passport-github2': 1.2.9
'@types/passport-google-oauth20': 2.0.14
'@types/querystringify': 2.0.2
'@types/ws': 8.5.11
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
@ -24268,6 +24279,7 @@ packages:
passport-google-oauth20: 2.0.0
prettier: 3.2.5
prettier-plugin-svelte: 3.2.1(prettier@3.2.5)(svelte@4.2.11)
querystringify: 2.2.0
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
@ -25340,7 +25352,7 @@ packages:
dev: false
file:projects/desktop.tgz(bufferutil@4.0.8)(sass@1.71.1)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-Zqlhi9UprYvF9L0ANw8sSkxTuqMYWTDmIUV6wYDTHd+NMt8NKshXy9J39mll8dW0h93TeY538RePwutrdvM4kA==, tarball: file:projects/desktop.tgz}
resolution: {integrity: sha512-j8onQxy51K29b/9q8g+lOG8e8dOFO2WpQQTdtnhP2vy2EcvIuSgckenQpg++0r1AtJ1G7FC7hr5QYNsrTPWkBA==, tarball: file:projects/desktop.tgz}
id: file:projects/desktop.tgz
name: '@rush-temp/desktop'
version: 0.0.0
@ -26820,7 +26832,7 @@ packages:
dev: false
file:projects/love-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-+jxDfVCF8l8WnKd4Xmaau521zbt9KBRubhiB6N8gXjePkAFSiFXdiQGPnB7VGyYLnImqDuCCnfQNJJpomb4TNQ==, tarball: file:projects/love-resources.tgz}
resolution: {integrity: sha512-e49VrhVkk6U8nj/ZyKfYVj/gK/3rTSrVCicDM5Luxw/f/8pPEVf9uEzmdAV11uiXuXxmj5t0BmHdG5eeVhiK6A==, tarball: file:projects/love-resources.tgz}
id: file:projects/love-resources.tgz
name: '@rush-temp/love-resources'
version: 0.0.0
@ -26868,7 +26880,7 @@ packages:
dev: false
file:projects/love.tgz(@types/node@20.11.19)(esbuild@0.20.1)(svelte@4.2.12)(ts-node@10.9.2):
resolution: {integrity: sha512-qWAfcld2pm2sf/2kZM1RLYGnhIJWRC42pBsrjQUmztuosCriG9qFa8fdymEsTp4TJaFG9zAqlci7/NKHN9ldvA==, tarball: file:projects/love.tgz}
resolution: {integrity: sha512-IA1DOwfYE00ACQ47zgXq3yOcNghk1oU1nPcq15bqdjOxJeKaE5sa4w/yJ7WoGn1Dm5F0TmxKc67GGDxcTT6kDQ==, tarball: file:projects/love.tgz}
id: file:projects/love.tgz
name: '@rush-temp/love'
version: 0.0.0
@ -27347,7 +27359,7 @@ packages:
dev: false
file:projects/model-love.tgz:
resolution: {integrity: sha512-iYxy2iQSrCHIlzZLKrDvP5yGrpJrVopD79gegg6iVsqnDbRqrTbsN2H69Xn1KeDbOJllKgVw5y8yVFO+AMq41Q==, tarball: file:projects/model-love.tgz}
resolution: {integrity: sha512-vHa71QNYOwrIifwIvVUmNWd+qlSgxZ0ErEgrx9uBSnJCxff9iqRhtmrFlfJ27bvi/jiBWOro9XYt5liL7gOGhg==, tarball: file:projects/model-love.tgz}
name: '@rush-temp/model-love'
version: 0.0.0
dependencies:
@ -28588,7 +28600,7 @@ packages:
dev: false
file:projects/onboard-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(file-loader@6.2.0)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)(webpack@5.90.3):
resolution: {integrity: sha512-3Gt9PEcA7T44mLBBC0KwwZQ6Qs7pXYbqQaagWFjfT+4MTvR9XVk4L+67U0yFYqpS/EeODSc+dJklk3QBY2O36g==, tarball: file:projects/onboard-resources.tgz}
resolution: {integrity: sha512-xbIj2phiY1rWzXxo5gqyucDzJS7IKMWhgTS4qZ5V9Rh02mCECL2Fs6N5OiiHFJH66jllxThPdX7SIDNBVPcfmA==, tarball: file:projects/onboard-resources.tgz}
id: file:projects/onboard-resources.tgz
name: '@rush-temp/onboard-resources'
version: 0.0.0
@ -28930,7 +28942,7 @@ packages:
dev: false
file:projects/pod-calendar.tgz(bufferutil@4.0.8)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-DOvMMTPpDOAuO8Vi8gEBTXBMQATYdZgtNWxvUyG0xbCArW9jEmKbrOVBV1ydZGOJqfqhennjv09+uWR/YUR+SQ==, tarball: file:projects/pod-calendar.tgz}
resolution: {integrity: sha512-rnCLCkBGWm2o9YTk6AnoYlNCACtncXiYw+CUkhVFJmBNTE1l90C74Uci4ldJBpxEOyCHVBzJIjsp6sMbQwafkw==, tarball: file:projects/pod-calendar.tgz}
id: file:projects/pod-calendar.tgz
name: '@rush-temp/pod-calendar'
version: 0.0.0
@ -29160,7 +29172,7 @@ packages:
dev: false
file:projects/pod-gmail.tgz(bufferutil@4.0.8)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-ybpB+uVlKzWvEVZnrp6iPuDYR7OwWBEsHl3ivSmte8BhfH9Q5QfzBm/FrEUsyEBIN4KS/cGDUoNGv5Us5OVWVw==, tarball: file:projects/pod-gmail.tgz}
resolution: {integrity: sha512-qeyt7Pl1TwEo/JojBg4R4LR3uWEdxOfqfz6tYB5kB27oEdE+jBJx0LGgsrDYoQSsv7A0LLnaFVfS+XT17EoKeQ==, tarball: file:projects/pod-gmail.tgz}
id: file:projects/pod-gmail.tgz
name: '@rush-temp/pod-gmail'
version: 0.0.0
@ -29448,7 +29460,7 @@ packages:
dev: false
file:projects/pod-telegram.tgz(bufferutil@4.0.8)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-mBbMXZhgRl0R9gvFN+uxfdFHgXdF9cFyC200rXnS4PgGkGVm9jRmE8lJQTxiOOJyX8rPDm8qlLhCrlKyof+pEQ==, tarball: file:projects/pod-telegram.tgz}
resolution: {integrity: sha512-MF+eEeVhFR4XQj2YaAP6gjvm1tijtnRUMDrQt48vZBD8mwQA8B0drOVHCkOQ+NJOYqi1c3pRG/+kPKWqswbplQ==, tarball: file:projects/pod-telegram.tgz}
id: file:projects/pod-telegram.tgz
name: '@rush-temp/pod-telegram'
version: 0.0.0
@ -31756,7 +31768,7 @@ packages:
dev: false
file:projects/server-pipeline.tgz:
resolution: {integrity: sha512-DpU8h5YS6u+d1uVTSyALfcGof3p2hXuSd8f1RyFI/YvNHxLHLrTu0AzquL8uhwxliB1bpNJtCWh1RWphdVJD4Q==, tarball: file:projects/server-pipeline.tgz}
resolution: {integrity: sha512-VMd/X1M3HotOPN51cVDAHjKrHhNm8/5GMMiZ4WklnQVtDfmDHocXKsC+FgOLXpb9RHYhtsz0Arwi7pW7xIar5A==, tarball: file:projects/server-pipeline.tgz}
name: '@rush-temp/server-pipeline'
version: 0.0.0
dependencies:

View File

@ -5,22 +5,23 @@
import { Loading, setMetadataLocalStorage } from '@hcengineering/ui'
import { onMount } from 'svelte'
import login from '../plugin'
import { afterConfirm, getSessionLoginInfo, goTo, navigateToWorkspace } from '../utils'
import { afterConfirm, getLoginInfoFromQuery, goTo, navigateToWorkspace } from '../utils'
onMount(async () => {
const result = await getSessionLoginInfo()
const result = await getLoginInfoFromQuery()
if (result !== undefined) {
if (isWorkspaceLoginInfo(result)) {
navigateToWorkspace(result.workspace, result)
navigateToWorkspace(result.workspace, result, undefined, true)
return
}
setMetadata(presentation.metadata.Token, result.token)
setMetadataLocalStorage(login.metadata.LastToken, result.token)
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
await afterConfirm()
await afterConfirm(true)
} else {
goTo('login')
goTo('login', true)
}
})

View File

@ -421,7 +421,12 @@ export function setLoginInfo (loginInfo: WorkspaceLoginInfo): void {
setMetadataLocalStorage(login.metadata.LoginEmail, loginInfo.email)
}
export function navigateToWorkspace (workspace: string, loginInfo?: WorkspaceLoginInfo, navigateUrl?: string): void {
export function navigateToWorkspace (
workspace: string,
loginInfo?: WorkspaceLoginInfo,
navigateUrl?: string,
replace = false
): void {
if (loginInfo == null) {
return
}
@ -432,7 +437,7 @@ export function navigateToWorkspace (workspace: string, loginInfo?: WorkspaceLog
try {
const loc = JSON.parse(decodeURIComponent(navigateUrl)) as Location
if (loc.path[1] === workspace) {
navigate(loc)
navigate(loc, replace)
return
}
} catch (err: any) {
@ -441,9 +446,9 @@ export function navigateToWorkspace (workspace: string, loginInfo?: WorkspaceLog
}
const last = localStorage.getItem(`${locationStorageKeyId}_${workspace}`)
if (last !== null) {
navigate(JSON.parse(last))
navigate(JSON.parse(last), replace)
} else {
navigate({ path: [workbenchId, workspace] })
navigate({ path: [workbenchId, workspace] }, replace)
}
}
@ -862,7 +867,7 @@ export function goTo (path: Pages, clearQuery: boolean = false): void {
if (clearQuery) {
loc.query = undefined
}
navigate(loc)
navigate(loc, clearQuery)
}
export function getHref (path: Pages): string {
@ -872,10 +877,10 @@ export function getHref (path: Pages): string {
return host + url
}
export async function afterConfirm (): Promise<void> {
export async function afterConfirm (clearQuery = false): Promise<void> {
const joinedWS = await getWorkspaces()
if (joinedWS.length === 0) {
goTo('createWorkspace')
goTo('createWorkspace', clearQuery)
} else if (joinedWS.length === 1) {
const result = (await selectWorkspace(joinedWS[0].workspace, null))[1]
if (result !== undefined) {
@ -883,29 +888,46 @@ export async function afterConfirm (): Promise<void> {
setMetadataLocalStorage(login.metadata.LastToken, result.token)
setLoginInfo(result)
navigateToWorkspace(joinedWS[0].workspace, result)
navigateToWorkspace(joinedWS[0].workspace, result, undefined, clearQuery)
}
} else {
goTo('selectWorkspace')
goTo('selectWorkspace', clearQuery)
}
}
export async function getSessionLoginInfo (): Promise<LoginInfo | WorkspaceLoginInfo | undefined> {
export async function getLoginInfoFromQuery (): Promise<LoginInfo | undefined> {
const token = getCurrentLocation().query?.token
if (token === undefined) {
return undefined
}
const accountsUrl = getMetadata(login.metadata.AccountsUrl)
if (accountsUrl === undefined) {
throw new Error('accounts url not specified')
}
const request = {
method: 'getAccountInfoByToken',
params: [] as any[]
}
try {
const response = await fetch(concatLink(accountsUrl, '/auth'), {
method: 'GET',
credentials: 'include'
const response = await fetch(accountsUrl, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
})
const result = await response.json()
return result
if (result.error != null) {
throw new PlatformError(result.error)
}
return result.result
} catch (err: any) {
console.error('login error', err)
Analytics.handleError(err)
}
}

View File

@ -1,6 +1,6 @@
<script lang="ts">
import login, { LoginInfo, WorkspaceLoginInfo } from '@hcengineering/login'
import { getSessionLoginInfo, navigateToWorkspace } from '@hcengineering/login-resources'
import { getLoginInfoFromQuery, navigateToWorkspace } from '@hcengineering/login-resources'
import { setMetadata } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import { Loading, setMetadataLocalStorage } from '@hcengineering/ui'
@ -8,19 +8,19 @@
import { afterConfirm, goToLogin } from '../utils'
onMount(async () => {
const result = await getSessionLoginInfo()
const result = await getLoginInfoFromQuery()
if (result !== undefined) {
if (isWorkspaceLoginInfo(result)) {
navigateToWorkspace(result.workspace, result)
navigateToWorkspace(result.workspace, result, undefined, true)
return
}
setMetadata(presentation.metadata.Token, result.token)
setMetadataLocalStorage(login.metadata.LastToken, result.token)
setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint)
setMetadataLocalStorage(login.metadata.LoginEmail, result.email)
await afterConfirm()
await afterConfirm(true)
} else {
goToLogin('login')
goToLogin('login', true)
}
})

View File

@ -49,7 +49,7 @@ export function goToLogin (page: LoginPages, clearQuery: boolean = false): void
if (clearQuery) {
loc.query = undefined
}
navigate(loc)
navigate(loc, clearQuery)
}
export function getHref (path: Pages): string {
@ -69,16 +69,16 @@ export async function ensureConfirmed (account: LoginInfo): Promise<void> {
}
}
export async function afterConfirm (): Promise<void> {
export async function afterConfirm (clearQuery = false): Promise<void> {
const joinedWS = await getWorkspaces()
if (joinedWS.length === 0) {
goTo('onboard')
goTo('onboard', clearQuery)
} else if (joinedWS.length === 1) {
const result = (await selectWorkspace(joinedWS[0].workspace, null))[1]
if (result !== undefined) {
navigateToWorkspace(joinedWS[0].workspace, result)
navigateToWorkspace(joinedWS[0].workspace, result, undefined, clearQuery)
}
} else {
goToLogin('selectWorkspace')
goToLogin('selectWorkspace', clearQuery)
}
}

View File

@ -42,7 +42,8 @@
"@types/koa-router": "^7.4.8",
"@types/koa": "^2.15.0",
"@types/koa-session": "^6.4.5",
"@types/passport-github2": "^1.2.9"
"@types/passport-github2": "^1.2.9",
"@types/querystringify": "^2.0.2"
},
"dependencies": {
"mongodb": "^6.8.0",
@ -54,6 +55,7 @@
"koa-passport": "^6.0.0",
"koa": "^2.15.3",
"koa-router": "^12.0.1",
"koa-session": "^6.4.0"
"koa-session": "^6.4.0",
"querystringify": "^2.2.0"
}
}

View File

@ -1,8 +1,9 @@
import { joinWithProvider, loginWithProvider } from '@hcengineering/account'
import { joinWithProvider, loginWithProvider, type LoginInfo } from '@hcengineering/account'
import { BrandingMap, concatLink, MeasureContext } from '@hcengineering/core'
import Router from 'koa-router'
import { Db } from 'mongodb'
import { Strategy as GitHubStrategy } from 'passport-github2'
import qs from 'querystringify'
import { Passport } from '.'
import { getBranding, getHost, safeParseAuthState } from './utils'
@ -66,10 +67,11 @@ export function registerGithub (
const [first, last] = ctx.state.user.displayName?.split(' ') ?? [ctx.state.user.username, '']
measureCtx.info('Provider auth handler', { email, type: 'github' })
if (email !== undefined) {
let loginInfo: LoginInfo
const state = safeParseAuthState(ctx.query?.state)
const branding = getBranding(brandings, state?.branding)
if (state.inviteId != null && state.inviteId !== '') {
const loginInfo = await joinWithProvider(
loginInfo = await joinWithProvider(
measureCtx,
db,
productId,
@ -82,20 +84,18 @@ export function registerGithub (
githubId: ctx.state.user.id
}
)
if (ctx.session != null) {
ctx.session.loginInfo = loginInfo
}
} else {
const loginInfo = await loginWithProvider(measureCtx, db, productId, null, email, first, last, {
loginInfo = await loginWithProvider(measureCtx, db, productId, null, email, first, last, {
githubId: ctx.state.user.id
})
if (ctx.session != null) {
ctx.session.loginInfo = loginInfo
}
}
measureCtx.info('Success auth, redirect', { email, type: 'github' })
const origin = concatLink(branding?.front ?? frontUrl, '/login/auth')
const query = encodeURIComponent(qs.stringify({ token: loginInfo.token }))
measureCtx.info('Success auth, redirect', { email, type: 'github', target: origin })
// Successful authentication, redirect to your application
ctx.redirect(concatLink(branding?.front ?? frontUrl, '/login/auth'))
ctx.redirect(`${origin}?${query}`)
}
} catch (err: any) {
measureCtx.error('failed to auth', { err, type: 'github', user: ctx.state?.user })

View File

@ -1,8 +1,9 @@
import { joinWithProvider, loginWithProvider } from '@hcengineering/account'
import { joinWithProvider, LoginInfo, loginWithProvider } from '@hcengineering/account'
import { BrandingMap, concatLink, MeasureContext } from '@hcengineering/core'
import Router from 'koa-router'
import { Db } from 'mongodb'
import { Strategy as GoogleStrategy } from 'passport-google-oauth20'
import qs from 'querystringify'
import { Passport } from '.'
import { getBranding, getHost, safeParseAuthState } from './utils'
@ -71,10 +72,11 @@ export function registerGoogle (
measureCtx.info('Provider auth handler', { email, type: 'google' })
if (email !== undefined) {
try {
let loginInfo: LoginInfo
const state = safeParseAuthState(ctx.query?.state)
const branding = getBranding(brandings, state?.branding)
if (state.inviteId != null && state.inviteId !== '') {
const loginInfo = await joinWithProvider(
loginInfo = await joinWithProvider(
measureCtx,
db,
productId,
@ -84,19 +86,16 @@ export function registerGoogle (
last,
state.inviteId as any
)
if (ctx.session != null) {
ctx.session.loginInfo = loginInfo
}
} else {
const loginInfo = await loginWithProvider(measureCtx, db, productId, null, email, first, last)
if (ctx.session != null) {
ctx.session.loginInfo = loginInfo
}
loginInfo = await loginWithProvider(measureCtx, db, productId, null, email, first, last)
}
const origin = concatLink(branding?.front ?? frontUrl, '/login/auth')
const query = encodeURIComponent(qs.stringify({ token: loginInfo.token }))
// Successful authentication, redirect to your application
measureCtx.info('Success auth, redirect', { email, type: 'google' })
ctx.redirect(concatLink(branding?.front ?? frontUrl, '/login/auth'))
measureCtx.info('Success auth, redirect', { email, type: 'google', target: origin })
ctx.redirect(`${origin}?${query}`)
} catch (err: any) {
measureCtx.error('failed to auth', { err, type: 'google', user: ctx.state?.user })
}

View File

@ -44,7 +44,6 @@ export function registerProviders (
app.keys = [serverSecret]
app.use(session({}, app))
app.use(passport.initialize())
app.use(passport.session())
@ -69,12 +68,6 @@ export function registerProviders (
if (value !== undefined) res.push(value)
}
router.get('auth', '/auth', (ctx) => {
if (ctx.session?.loginInfo != null) {
ctx.body = JSON.stringify(ctx.session.loginInfo)
}
})
router.get('providers', '/providers', (ctx) => {
ctx.body = JSON.stringify(res)
})

View File

@ -2,6 +2,7 @@ import { getAccountInfoByToken } from '@hcengineering/account'
import { BrandingMap, concatLink, MeasureContext } from '@hcengineering/core'
import Router from 'koa-router'
import { Db } from 'mongodb'
import qs from 'querystringify'
import { Strategy as CustomStrategy } from 'passport-custom'
import { Passport } from '.'
import { getBranding, getHost, safeParseAuthState } from './utils'
@ -49,13 +50,12 @@ export function registerToken (
const state = safeParseAuthState(ctx.query?.state)
const branding = getBranding(brandings, state?.branding)
if (ctx.session != null) {
ctx.session.loginInfo = user
}
const origin = concatLink(branding?.front ?? frontUrl, '/onboard/auth')
const query = encodeURIComponent(qs.stringify({ token: user.token }))
measureCtx.info('Success auth, redirect', { email: user.email, type: 'token' })
measureCtx.info('Success auth, redirect', { email: user.email, type: 'token', target: origin })
// Successful authentication, redirect to your application
ctx.redirect(concatLink(branding?.front ?? frontUrl, '/onboard/auth'))
ctx.redirect(`${origin}?${query}`)
}
await next()
}