Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
33b208ab
Commit
33b208ab
authored
Apr 21, 2026
by
IanShaw027
Browse files
fix: restore legacy oauth callback fragment compatibility
parent
f3986501
Changes
4
Hide whitespace changes
Inline
Side-by-side
frontend/src/views/auth/LinuxDoCallbackView.vue
View file @
33b208ab
...
@@ -249,6 +249,7 @@ import {
...
@@ -249,6 +249,7 @@ import {
login2FA
,
login2FA
,
persistOAuthTokenContext
,
persistOAuthTokenContext
,
type
OAuthAdoptionDecision
,
type
OAuthAdoptionDecision
,
type
OAuthTokenResponse
,
type
PendingOAuthExchangeResponse
type
PendingOAuthExchangeResponse
}
from
'
@/api/auth
'
}
from
'
@/api/auth
'
...
@@ -278,6 +279,7 @@ const pendingAccountAction = ref<'none' | 'create_account' | 'bind_login'>('none
...
@@ -278,6 +279,7 @@ const pendingAccountAction = ref<'none' | 'create_account' | 'bind_login'>('none
const
pendingAccountEmail
=
ref
(
''
)
const
pendingAccountEmail
=
ref
(
''
)
const
bindLoginEmail
=
ref
(
''
)
const
bindLoginEmail
=
ref
(
''
)
const
bindLoginPassword
=
ref
(
''
)
const
bindLoginPassword
=
ref
(
''
)
const
legacyPendingOAuthToken
=
ref
(
''
)
const
accountActionError
=
ref
(
''
)
const
accountActionError
=
ref
(
''
)
const
canReturnToCreateAccount
=
ref
(
false
)
const
canReturnToCreateAccount
=
ref
(
false
)
const
bindSuccessMessage
=
t
(
'
profile.authBindings.bindSuccess
'
)
const
bindSuccessMessage
=
t
(
'
profile.authBindings.bindSuccess
'
)
...
@@ -315,6 +317,30 @@ function parseFragmentParams(): URLSearchParams {
...
@@ -315,6 +317,30 @@ function parseFragmentParams(): URLSearchParams {
return
new
URLSearchParams
(
hash
)
return
new
URLSearchParams
(
hash
)
}
}
function
readLegacyFragmentLogin
(
params
:
URLSearchParams
):
OAuthTokenResponse
|
null
{
const
accessToken
=
params
.
get
(
'
access_token
'
)?.
trim
()
||
''
if
(
!
accessToken
)
{
return
null
}
const
completion
:
OAuthTokenResponse
=
{
access_token
:
accessToken
}
const
refreshToken
=
params
.
get
(
'
refresh_token
'
)?.
trim
()
||
''
if
(
refreshToken
)
{
completion
.
refresh_token
=
refreshToken
}
const
expiresIn
=
Number
.
parseInt
(
params
.
get
(
'
expires_in
'
)?.
trim
()
||
''
,
10
)
if
(
Number
.
isFinite
(
expiresIn
)
&&
expiresIn
>
0
)
{
completion
.
expires_in
=
expiresIn
}
const
tokenType
=
params
.
get
(
'
token_type
'
)?.
trim
()
||
''
if
(
tokenType
)
{
completion
.
token_type
=
tokenType
}
return
completion
}
function
sanitizeRedirectPath
(
path
:
string
|
null
|
undefined
):
string
{
function
sanitizeRedirectPath
(
path
:
string
|
null
|
undefined
):
string
{
if
(
!
path
)
return
'
/dashboard
'
if
(
!
path
)
return
'
/dashboard
'
if
(
!
path
.
startsWith
(
'
/
'
))
return
'
/dashboard
'
if
(
!
path
.
startsWith
(
'
/
'
))
return
'
/dashboard
'
...
@@ -521,10 +547,18 @@ async function handleSubmitInvitation() {
...
@@ -521,10 +547,18 @@ async function handleSubmitInvitation() {
isSubmitting
.
value
=
true
isSubmitting
.
value
=
true
try
{
try
{
const
tokenData
=
await
completeLinuxDoOAuthRegistration
(
const
tokenData
=
legacyPendingOAuthToken
.
value
invitationCode
.
value
.
trim
(),
?
(
currentAdoptionDecision
()
await
apiClient
.
post
<
OAuthTokenResponse
>
(
'
/auth/oauth/linuxdo/complete-registration
'
,
{
)
pending_oauth_token
:
legacyPendingOAuthToken
.
value
,
invitation_code
:
invitationCode
.
value
.
trim
(),
...
serializeAdoptionDecision
(
currentAdoptionDecision
())
})
).
data
:
await
completeLinuxDoOAuthRegistration
(
invitationCode
.
value
.
trim
(),
currentAdoptionDecision
()
)
persistOAuthTokenContext
(
tokenData
)
persistOAuthTokenContext
(
tokenData
)
await
authStore
.
setToken
(
tokenData
.
access_token
)
await
authStore
.
setToken
(
tokenData
.
access_token
)
appStore
.
showSuccess
(
t
(
'
auth.loginSuccess
'
))
appStore
.
showSuccess
(
t
(
'
auth.loginSuccess
'
))
...
@@ -621,51 +655,72 @@ async function handleSubmitTotpChallenge() {
...
@@ -621,51 +655,72 @@ async function handleSubmitTotpChallenge() {
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
const
params
=
parseFragmentParams
()
const
params
=
parseFragmentParams
()
const
legacyLogin
=
readLegacyFragmentLogin
(
params
)
const
legacyPendingToken
=
params
.
get
(
'
pending_oauth_token
'
)?.
trim
()
||
''
const
error
=
params
.
get
(
'
error
'
)
const
error
=
params
.
get
(
'
error
'
)
const
errorDesc
=
params
.
get
(
'
error_description
'
)
||
params
.
get
(
'
error_message
'
)
||
''
const
errorDesc
=
params
.
get
(
'
error_description
'
)
||
params
.
get
(
'
error_message
'
)
||
''
const
redirect
=
sanitizeRedirectPath
(
if
(
error
)
{
params
.
get
(
'
redirect
'
)
||
(
route
.
query
.
redirect
as
string
|
undefined
)
||
'
/dashboard
'
errorMessage
.
value
=
errorDesc
||
error
)
appStore
.
showError
(
errorMessage
.
value
)
isProcessing
.
value
=
false
return
}
try
{
try
{
if
(
legacyLogin
)
{
persistOAuthTokenContext
(
legacyLogin
)
await
authStore
.
setToken
(
legacyLogin
.
access_token
)
appStore
.
showSuccess
(
t
(
'
auth.loginSuccess
'
))
await
router
.
replace
(
redirect
)
return
}
if
(
error
===
'
invitation_required
'
&&
legacyPendingToken
)
{
legacyPendingOAuthToken
.
value
=
legacyPendingToken
redirectTo
.
value
=
redirect
needsInvitation
.
value
=
true
isProcessing
.
value
=
false
return
}
if
(
error
)
{
errorMessage
.
value
=
errorDesc
||
error
appStore
.
showError
(
errorMessage
.
value
)
isProcessing
.
value
=
false
return
}
const
completion
=
await
exchangePendingOAuthCompletion
()
const
completion
=
await
exchangePendingOAuthCompletion
()
const
r
edirect
=
sanitizeRedirectPath
(
const
completionR
edirect
=
sanitizeRedirectPath
(
completion
.
redirect
||
(
route
.
query
.
redirect
as
string
|
undefined
)
||
'
/dashboard
'
completion
.
redirect
||
(
route
.
query
.
redirect
as
string
|
undefined
)
||
'
/dashboard
'
)
)
applyAdoptionSuggestionState
(
completion
)
applyAdoptionSuggestionState
(
completion
)
redirectTo
.
value
=
r
edirect
redirectTo
.
value
=
completionR
edirect
if
(
completion
.
error
===
'
invitation_required
'
)
{
if
(
completion
.
error
===
'
invitation_required
'
)
{
needsInvitation
.
value
=
true
needsInvitation
.
value
=
true
isProcessing
.
value
=
false
isProcessing
.
value
=
false
persistPendingAuthSession
(
r
edirect
)
persistPendingAuthSession
(
completionR
edirect
)
return
return
}
}
if
(
applyTotpChallenge
(
completion
as
LinuxDoPendingActionResponse
))
{
if
(
applyTotpChallenge
(
completion
as
LinuxDoPendingActionResponse
))
{
persistPendingAuthSession
(
r
edirect
)
persistPendingAuthSession
(
completionR
edirect
)
return
return
}
}
applyPendingAccountAction
(
completion
as
LinuxDoPendingActionResponse
)
applyPendingAccountAction
(
completion
as
LinuxDoPendingActionResponse
)
if
(
pendingAccountAction
.
value
!==
'
none
'
)
{
if
(
pendingAccountAction
.
value
!==
'
none
'
)
{
isProcessing
.
value
=
false
isProcessing
.
value
=
false
persistPendingAuthSession
(
r
edirect
)
persistPendingAuthSession
(
completionR
edirect
)
return
return
}
}
if
(
adoptionRequired
.
value
&&
hasSuggestedProfile
(
completion
))
{
if
(
adoptionRequired
.
value
&&
hasSuggestedProfile
(
completion
))
{
needsAdoptionConfirmation
.
value
=
true
needsAdoptionConfirmation
.
value
=
true
isProcessing
.
value
=
false
isProcessing
.
value
=
false
persistPendingAuthSession
(
r
edirect
)
persistPendingAuthSession
(
completionR
edirect
)
return
return
}
}
await
finalizeCompletion
(
completion
,
r
edirect
)
await
finalizeCompletion
(
completion
,
completionR
edirect
)
}
catch
(
e
:
unknown
)
{
}
catch
(
e
:
unknown
)
{
clearPendingAuthSession
()
clearPendingAuthSession
()
errorMessage
.
value
=
getRequestErrorMessage
(
e
,
t
(
'
auth.loginFailed
'
))
errorMessage
.
value
=
getRequestErrorMessage
(
e
,
t
(
'
auth.loginFailed
'
))
...
...
frontend/src/views/auth/OidcCallbackView.vue
View file @
33b208ab
...
@@ -259,6 +259,7 @@ import {
...
@@ -259,6 +259,7 @@ import {
login2FA
,
login2FA
,
persistOAuthTokenContext
,
persistOAuthTokenContext
,
type
OAuthAdoptionDecision
,
type
OAuthAdoptionDecision
,
type
OAuthTokenResponse
,
type
PendingOAuthExchangeResponse
type
PendingOAuthExchangeResponse
}
from
'
@/api/auth
'
}
from
'
@/api/auth
'
...
@@ -287,6 +288,7 @@ const pendingAccountAction = ref<'none' | 'create_account' | 'bind_login'>('none
...
@@ -287,6 +288,7 @@ const pendingAccountAction = ref<'none' | 'create_account' | 'bind_login'>('none
const
pendingAccountEmail
=
ref
(
''
)
const
pendingAccountEmail
=
ref
(
''
)
const
bindLoginEmail
=
ref
(
''
)
const
bindLoginEmail
=
ref
(
''
)
const
bindLoginPassword
=
ref
(
''
)
const
bindLoginPassword
=
ref
(
''
)
const
legacyPendingOAuthToken
=
ref
(
''
)
const
accountActionError
=
ref
(
''
)
const
accountActionError
=
ref
(
''
)
const
canReturnToCreateAccount
=
ref
(
false
)
const
canReturnToCreateAccount
=
ref
(
false
)
const
bindSuccessMessage
=
t
(
'
profile.authBindings.bindSuccess
'
)
const
bindSuccessMessage
=
t
(
'
profile.authBindings.bindSuccess
'
)
...
@@ -331,6 +333,30 @@ function parseFragmentParams(): URLSearchParams {
...
@@ -331,6 +333,30 @@ function parseFragmentParams(): URLSearchParams {
return
new
URLSearchParams
(
hash
)
return
new
URLSearchParams
(
hash
)
}
}
function
readLegacyFragmentLogin
(
params
:
URLSearchParams
):
OAuthTokenResponse
|
null
{
const
accessToken
=
params
.
get
(
'
access_token
'
)?.
trim
()
||
''
if
(
!
accessToken
)
{
return
null
}
const
completion
:
OAuthTokenResponse
=
{
access_token
:
accessToken
}
const
refreshToken
=
params
.
get
(
'
refresh_token
'
)?.
trim
()
||
''
if
(
refreshToken
)
{
completion
.
refresh_token
=
refreshToken
}
const
expiresIn
=
Number
.
parseInt
(
params
.
get
(
'
expires_in
'
)?.
trim
()
||
''
,
10
)
if
(
Number
.
isFinite
(
expiresIn
)
&&
expiresIn
>
0
)
{
completion
.
expires_in
=
expiresIn
}
const
tokenType
=
params
.
get
(
'
token_type
'
)?.
trim
()
||
''
if
(
tokenType
)
{
completion
.
token_type
=
tokenType
}
return
completion
}
function
sanitizeRedirectPath
(
path
:
string
|
null
|
undefined
):
string
{
function
sanitizeRedirectPath
(
path
:
string
|
null
|
undefined
):
string
{
if
(
!
path
)
return
'
/dashboard
'
if
(
!
path
)
return
'
/dashboard
'
if
(
!
path
.
startsWith
(
'
/
'
))
return
'
/dashboard
'
if
(
!
path
.
startsWith
(
'
/
'
))
return
'
/dashboard
'
...
@@ -565,10 +591,18 @@ async function handleSubmitInvitation() {
...
@@ -565,10 +591,18 @@ async function handleSubmitInvitation() {
isSubmitting
.
value
=
true
isSubmitting
.
value
=
true
try
{
try
{
const
tokenData
=
await
completeOIDCOAuthRegistration
(
const
tokenData
=
legacyPendingOAuthToken
.
value
invitationCode
.
value
.
trim
(),
?
(
currentAdoptionDecision
()
await
apiClient
.
post
<
OAuthTokenResponse
>
(
'
/auth/oauth/oidc/complete-registration
'
,
{
)
pending_oauth_token
:
legacyPendingOAuthToken
.
value
,
invitation_code
:
invitationCode
.
value
.
trim
(),
...
serializeAdoptionDecision
(
currentAdoptionDecision
())
}
)
).
data
:
await
completeOIDCOAuthRegistration
(
invitationCode
.
value
.
trim
(),
currentAdoptionDecision
()
)
persistOAuthTokenContext
(
tokenData
)
persistOAuthTokenContext
(
tokenData
)
await
authStore
.
setToken
(
tokenData
.
access_token
)
await
authStore
.
setToken
(
tokenData
.
access_token
)
appStore
.
showSuccess
(
t
(
'
auth.loginSuccess
'
))
appStore
.
showSuccess
(
t
(
'
auth.loginSuccess
'
))
...
@@ -667,51 +701,72 @@ onMounted(async () => {
...
@@ -667,51 +701,72 @@ onMounted(async () => {
void
loadProviderName
()
void
loadProviderName
()
const
params
=
parseFragmentParams
()
const
params
=
parseFragmentParams
()
const
legacyLogin
=
readLegacyFragmentLogin
(
params
)
const
legacyPendingToken
=
params
.
get
(
'
pending_oauth_token
'
)?.
trim
()
||
''
const
error
=
params
.
get
(
'
error
'
)
const
error
=
params
.
get
(
'
error
'
)
const
errorDesc
=
params
.
get
(
'
error_description
'
)
||
params
.
get
(
'
error_message
'
)
||
''
const
errorDesc
=
params
.
get
(
'
error_description
'
)
||
params
.
get
(
'
error_message
'
)
||
''
const
redirect
=
sanitizeRedirectPath
(
if
(
error
)
{
params
.
get
(
'
redirect
'
)
||
(
route
.
query
.
redirect
as
string
|
undefined
)
||
'
/dashboard
'
errorMessage
.
value
=
errorDesc
||
error
)
appStore
.
showError
(
errorMessage
.
value
)
isProcessing
.
value
=
false
return
}
try
{
try
{
if
(
legacyLogin
)
{
persistOAuthTokenContext
(
legacyLogin
)
await
authStore
.
setToken
(
legacyLogin
.
access_token
)
appStore
.
showSuccess
(
t
(
'
auth.loginSuccess
'
))
await
router
.
replace
(
redirect
)
return
}
if
(
error
===
'
invitation_required
'
&&
legacyPendingToken
)
{
legacyPendingOAuthToken
.
value
=
legacyPendingToken
redirectTo
.
value
=
redirect
needsInvitation
.
value
=
true
isProcessing
.
value
=
false
return
}
if
(
error
)
{
errorMessage
.
value
=
errorDesc
||
error
appStore
.
showError
(
errorMessage
.
value
)
isProcessing
.
value
=
false
return
}
const
completion
=
await
exchangePendingOAuthCompletion
()
as
PendingOidcCompletion
const
completion
=
await
exchangePendingOAuthCompletion
()
as
PendingOidcCompletion
const
r
edirect
=
sanitizeRedirectPath
(
const
completionR
edirect
=
sanitizeRedirectPath
(
completion
.
redirect
||
(
route
.
query
.
redirect
as
string
|
undefined
)
||
'
/dashboard
'
completion
.
redirect
||
(
route
.
query
.
redirect
as
string
|
undefined
)
||
'
/dashboard
'
)
)
applyAdoptionSuggestionState
(
completion
)
applyAdoptionSuggestionState
(
completion
)
redirectTo
.
value
=
r
edirect
redirectTo
.
value
=
completionR
edirect
if
(
completion
.
error
===
'
invitation_required
'
)
{
if
(
completion
.
error
===
'
invitation_required
'
)
{
needsInvitation
.
value
=
true
needsInvitation
.
value
=
true
isProcessing
.
value
=
false
isProcessing
.
value
=
false
persistPendingAuthSession
(
r
edirect
)
persistPendingAuthSession
(
completionR
edirect
)
return
return
}
}
if
(
applyTotpChallenge
(
completion
))
{
if
(
applyTotpChallenge
(
completion
))
{
persistPendingAuthSession
(
r
edirect
)
persistPendingAuthSession
(
completionR
edirect
)
return
return
}
}
applyPendingAccountAction
(
completion
)
applyPendingAccountAction
(
completion
)
if
(
pendingAccountAction
.
value
!==
'
none
'
)
{
if
(
pendingAccountAction
.
value
!==
'
none
'
)
{
isProcessing
.
value
=
false
isProcessing
.
value
=
false
persistPendingAuthSession
(
r
edirect
)
persistPendingAuthSession
(
completionR
edirect
)
return
return
}
}
if
(
adoptionRequired
.
value
&&
hasSuggestedProfile
(
completion
))
{
if
(
adoptionRequired
.
value
&&
hasSuggestedProfile
(
completion
))
{
needsAdoptionConfirmation
.
value
=
true
needsAdoptionConfirmation
.
value
=
true
isProcessing
.
value
=
false
isProcessing
.
value
=
false
persistPendingAuthSession
(
r
edirect
)
persistPendingAuthSession
(
completionR
edirect
)
return
return
}
}
await
finalizeCompletion
(
completion
,
r
edirect
)
await
finalizeCompletion
(
completion
,
completionR
edirect
)
}
catch
(
e
:
unknown
)
{
}
catch
(
e
:
unknown
)
{
clearPendingAuthSession
()
clearPendingAuthSession
()
errorMessage
.
value
=
getRequestErrorMessage
(
e
,
t
(
'
auth.loginFailed
'
))
errorMessage
.
value
=
getRequestErrorMessage
(
e
,
t
(
'
auth.loginFailed
'
))
...
...
frontend/src/views/auth/__tests__/LinuxDoCallbackView.spec.ts
View file @
33b208ab
...
@@ -86,6 +86,74 @@ describe('LinuxDoCallbackView', () => {
...
@@ -86,6 +86,74 @@ describe('LinuxDoCallbackView', () => {
turnstile_enabled
:
false
,
turnstile_enabled
:
false
,
turnstile_site_key
:
''
turnstile_site_key
:
''
})
})
window
.
location
.
hash
=
''
localStorage
.
clear
()
})
it
(
'
accepts the legacy fragment token success callback without pending-session exchange
'
,
async
()
=>
{
window
.
location
.
hash
=
'
#access_token=legacy-access-token&refresh_token=legacy-refresh-token&expires_in=3600&token_type=Bearer&redirect=%2Flegacy-dashboard
'
setToken
.
mockResolvedValue
({})
mount
(
LinuxDoCallbackView
,
{
global
:
{
stubs
:
{
AuthLayout
:
{
template
:
'
<div><slot /></div>
'
},
Icon
:
true
,
RouterLink
:
{
template
:
'
<a><slot /></a>
'
},
transition
:
false
}
}
})
await
flushPromises
()
expect
(
exchangePendingOAuthCompletion
).
not
.
toHaveBeenCalled
()
expect
(
setToken
).
toHaveBeenCalledWith
(
'
legacy-access-token
'
)
expect
(
localStorage
.
getItem
(
'
refresh_token
'
)).
toBe
(
'
legacy-refresh-token
'
)
expect
(
localStorage
.
getItem
(
'
token_expires_at
'
)).
not
.
toBeNull
()
expect
(
showSuccess
).
toHaveBeenCalledWith
(
'
auth.loginSuccess
'
)
expect
(
replace
).
toHaveBeenCalledWith
(
'
/legacy-dashboard
'
)
})
it
(
'
accepts the legacy pending oauth invitation fragment without pending-session exchange
'
,
async
()
=>
{
window
.
location
.
hash
=
'
#error=invitation_required&pending_oauth_token=legacy-pending-token&redirect=%2Flegacy-invite
'
apiClientPost
.
mockResolvedValue
({
data
:
{
access_token
:
'
legacy-access-token
'
,
refresh_token
:
'
legacy-refresh-token
'
,
expires_in
:
3600
,
token_type
:
'
Bearer
'
}
})
setToken
.
mockResolvedValue
({})
const
wrapper
=
mount
(
LinuxDoCallbackView
,
{
global
:
{
stubs
:
{
AuthLayout
:
{
template
:
'
<div><slot /></div>
'
},
Icon
:
true
,
RouterLink
:
{
template
:
'
<a><slot /></a>
'
},
transition
:
false
}
}
})
await
flushPromises
()
expect
(
exchangePendingOAuthCompletion
).
not
.
toHaveBeenCalled
()
await
wrapper
.
find
(
'
input[type="text"]
'
).
setValue
(
'
invite-code
'
)
await
wrapper
.
find
(
'
button
'
).
trigger
(
'
click
'
)
await
flushPromises
()
expect
(
apiClientPost
).
toHaveBeenCalledWith
(
'
/auth/oauth/linuxdo/complete-registration
'
,
{
adopt_display_name
:
true
,
adopt_avatar
:
true
,
pending_oauth_token
:
'
legacy-pending-token
'
,
invitation_code
:
'
invite-code
'
})
expect
(
setToken
).
toHaveBeenCalledWith
(
'
legacy-access-token
'
)
expect
(
replace
).
toHaveBeenCalledWith
(
'
/legacy-invite
'
)
})
})
it
(
'
does not send adoption decisions during the initial exchange
'
,
async
()
=>
{
it
(
'
does not send adoption decisions during the initial exchange
'
,
async
()
=>
{
...
...
frontend/src/views/auth/__tests__/OidcCallbackView.spec.ts
View file @
33b208ab
...
@@ -92,6 +92,74 @@ describe('OidcCallbackView', () => {
...
@@ -92,6 +92,74 @@ describe('OidcCallbackView', () => {
turnstile_enabled
:
false
,
turnstile_enabled
:
false
,
turnstile_site_key
:
''
turnstile_site_key
:
''
})
})
window
.
location
.
hash
=
''
localStorage
.
clear
()
})
it
(
'
accepts the legacy fragment token success callback without pending-session exchange
'
,
async
()
=>
{
window
.
location
.
hash
=
'
#access_token=legacy-access-token&refresh_token=legacy-refresh-token&expires_in=3600&token_type=Bearer&redirect=%2Flegacy-dashboard
'
setToken
.
mockResolvedValue
({})
mount
(
OidcCallbackView
,
{
global
:
{
stubs
:
{
AuthLayout
:
{
template
:
'
<div><slot /></div>
'
},
Icon
:
true
,
RouterLink
:
{
template
:
'
<a><slot /></a>
'
},
transition
:
false
}
}
})
await
flushPromises
()
expect
(
exchangePendingOAuthCompletion
).
not
.
toHaveBeenCalled
()
expect
(
setToken
).
toHaveBeenCalledWith
(
'
legacy-access-token
'
)
expect
(
localStorage
.
getItem
(
'
refresh_token
'
)).
toBe
(
'
legacy-refresh-token
'
)
expect
(
localStorage
.
getItem
(
'
token_expires_at
'
)).
not
.
toBeNull
()
expect
(
showSuccess
).
toHaveBeenCalledWith
(
'
auth.loginSuccess
'
)
expect
(
replace
).
toHaveBeenCalledWith
(
'
/legacy-dashboard
'
)
})
it
(
'
accepts the legacy pending oauth invitation fragment without pending-session exchange
'
,
async
()
=>
{
window
.
location
.
hash
=
'
#error=invitation_required&pending_oauth_token=legacy-pending-token&redirect=%2Flegacy-invite
'
apiClientPost
.
mockResolvedValue
({
data
:
{
access_token
:
'
legacy-access-token
'
,
refresh_token
:
'
legacy-refresh-token
'
,
expires_in
:
3600
,
token_type
:
'
Bearer
'
}
})
setToken
.
mockResolvedValue
({})
const
wrapper
=
mount
(
OidcCallbackView
,
{
global
:
{
stubs
:
{
AuthLayout
:
{
template
:
'
<div><slot /></div>
'
},
Icon
:
true
,
RouterLink
:
{
template
:
'
<a><slot /></a>
'
},
transition
:
false
}
}
})
await
flushPromises
()
expect
(
exchangePendingOAuthCompletion
).
not
.
toHaveBeenCalled
()
await
wrapper
.
find
(
'
input[type="text"]
'
).
setValue
(
'
invite-code
'
)
await
wrapper
.
find
(
'
button
'
).
trigger
(
'
click
'
)
await
flushPromises
()
expect
(
apiClientPost
).
toHaveBeenCalledWith
(
'
/auth/oauth/oidc/complete-registration
'
,
{
adopt_display_name
:
true
,
adopt_avatar
:
true
,
pending_oauth_token
:
'
legacy-pending-token
'
,
invitation_code
:
'
invite-code
'
})
expect
(
setToken
).
toHaveBeenCalledWith
(
'
legacy-access-token
'
)
expect
(
replace
).
toHaveBeenCalledWith
(
'
/legacy-invite
'
)
})
})
it
(
'
does not send adoption decisions during the initial exchange
'
,
async
()
=>
{
it
(
'
does not send adoption decisions during the initial exchange
'
,
async
()
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment