Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
33b095e4
Commit
33b095e4
authored
Mar 23, 2022
by
Paul Gascou-Vaillancourt
Committed by
Paul Gascou-Vaillancourt
Mar 25, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor with Vue
Apply @pslaughter's patch to refactor the challenge logic with a Vue component.
parent
4e6e375a
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
257 additions
and
205 deletions
+257
-205
ee/app/assets/javascripts/arkose_labs/arkose_labs.js
ee/app/assets/javascripts/arkose_labs/arkose_labs.js
+0
-199
ee/app/assets/javascripts/arkose_labs/components/sign_in_arkose_app.vue
...javascripts/arkose_labs/components/sign_in_arkose_app.vue
+193
-0
ee/app/assets/javascripts/arkose_labs/index.js
ee/app/assets/javascripts/arkose_labs/index.js
+31
-0
ee/app/assets/javascripts/arkose_labs/init_arkose_labs_script.js
...assets/javascripts/arkose_labs/init_arkose_labs_script.js
+26
-0
ee/app/assets/javascripts/pages/sessions/new/index.js
ee/app/assets/javascripts/pages/sessions/new/index.js
+6
-5
ee/app/views/devise/sessions/_arkose_labs.html.haml
ee/app/views/devise/sessions/_arkose_labs.html.haml
+1
-1
No files found.
ee/app/assets/javascripts/arkose_labs/arkose_labs.js
deleted
100644 → 0
View file @
4e6e375a
import
Vue
from
'
vue
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
needsArkoseLabsChallenge
}
from
'
ee/rest_api
'
;
import
{
loadingIconForLegacyJS
}
from
'
~/loading_icon_for_legacy_js
'
;
import
{
__
}
from
'
~/locale
'
;
const
VERIFICATION_TOKEN_INPUT_NAME
=
'
arkose_labs_token
'
;
const
LOADING_ICON
=
loadingIconForLegacyJS
({
classes
:
[
'
gl-mr-2
'
]
});
const
CHALLENGE_ERRORS_CONTAINER_CLASS
=
'
js-arkose-labs-error-message
'
;
export
class
ArkoseLabs
{
constructor
()
{
this
.
signInForm
=
document
.
querySelector
(
'
.js-sign-in-form
'
);
if
(
!
this
.
signInForm
)
{
return
;
}
this
.
usernameField
=
this
.
signInForm
.
querySelector
(
'
.js-username-field
'
);
this
.
arkoseLabsChallengeContainer
=
this
.
signInForm
.
querySelector
(
'
.js-arkose-labs-challenge
'
);
this
.
signInButton
=
this
.
signInForm
.
querySelector
(
'
.js-sign-in-button
'
);
this
.
onUsernameFieldBlur
=
this
.
onUsernameFieldBlur
.
bind
(
this
);
this
.
onSignInFormSubmitted
=
this
.
onSignInFormSubmitted
.
bind
(
this
);
this
.
setConfig
=
this
.
setConfig
.
bind
(
this
);
this
.
passArkoseLabsChallenge
=
this
.
passArkoseLabsChallenge
.
bind
(
this
);
this
.
handleArkoseLabsFailure
=
this
.
handleArkoseLabsFailure
.
bind
(
this
);
this
.
publicKey
=
this
.
arkoseLabsChallengeContainer
.
dataset
.
apiKey
;
this
.
username
=
this
.
usernameField
.
value
||
''
;
this
.
arkoseLabsInitialized
=
false
;
this
.
arkoseLabsChallengePassed
=
false
;
window
.
setupArkoseLabsEnforcement
=
this
.
setConfig
;
this
.
attachEventListeners
();
if
(
this
.
username
.
length
)
{
this
.
checkIfNeedsChallenge
();
}
}
attachEventListeners
()
{
this
.
usernameField
.
addEventListener
(
'
blur
'
,
this
.
onUsernameFieldBlur
);
this
.
signInForm
.
addEventListener
(
'
submit
'
,
this
.
onSignInFormSubmitted
);
}
detachEventListeners
()
{
this
.
usernameField
.
removeEventListener
(
'
blur
'
,
this
.
onUsernameFieldBlur
);
this
.
signInForm
.
removeEventListener
(
'
submit
'
,
this
.
onSignInFormSubmitted
);
}
onUsernameFieldBlur
()
{
const
{
value
}
=
this
.
usernameField
;
if
(
this
.
username
!==
this
.
usernameField
.
value
)
{
this
.
username
=
value
;
this
.
checkIfNeedsChallenge
();
}
}
onSignInFormSubmitted
(
e
)
{
if
(
!
this
.
arkoseLabsInitialized
||
this
.
arkoseLabsChallengePassed
)
{
return
;
}
e
.
preventDefault
();
this
.
showArkoseLabsErrorMessage
();
}
async
checkIfNeedsChallenge
()
{
if
(
this
.
arkoseLabsInitialized
)
{
return
;
}
this
.
setButtonLoadingState
();
try
{
const
{
data
:
{
result
},
}
=
await
needsArkoseLabsChallenge
(
this
.
username
);
if
(
result
)
{
this
.
initArkoseLabsChallenge
();
}
}
catch
{
// API call failed, do not initialize Arkose challenge.
// Button will be reset in `finally` block.
}
finally
{
this
.
resetButton
();
}
}
setButtonLoadingState
()
{
const
label
=
__
(
'
Loading
'
);
this
.
signInButton
.
innerHTML
=
`
${
LOADING_ICON
.
outerHTML
}
${
label
}
`
;
this
.
signInButton
.
setAttribute
(
'
disabled
'
,
true
);
}
resetButton
()
{
this
.
signInButton
.
innerText
=
__
(
'
Sign in
'
);
this
.
signInButton
.
removeAttribute
(
'
disabled
'
);
}
initArkoseLabsChallenge
()
{
this
.
arkoseLabsInitialized
=
true
;
const
tag
=
document
.
createElement
(
'
script
'
);
[
[
'
type
'
,
'
text/javascript
'
],
[
'
src
'
,
`https://client-api.arkoselabs.com/v2/
${
this
.
publicKey
}
/api.js`
],
[
'
nonce
'
,
true
],
[
'
async
'
,
true
],
[
'
defer
'
,
true
],
[
'
data-callback
'
,
'
setupArkoseLabsEnforcement
'
],
].
forEach
(([
attr
,
value
])
=>
{
tag
.
setAttribute
(
attr
,
value
);
});
document
.
head
.
appendChild
(
tag
);
const
tokenInput
=
document
.
createElement
(
'
input
'
);
tokenInput
.
name
=
VERIFICATION_TOKEN_INPUT_NAME
;
tokenInput
.
setAttribute
(
'
type
'
,
'
hidden
'
);
this
.
tokenInput
=
tokenInput
;
this
.
signInForm
.
appendChild
(
tokenInput
);
}
setConfig
(
enforcement
)
{
enforcement
.
setConfig
({
mode
:
'
inline
'
,
selector
:
'
.js-arkose-labs-challenge
'
,
onShown
:
()
=>
{
this
.
arkoseLabsChallengeContainer
.
classList
.
remove
(
'
gl-display-none!
'
);
},
onCompleted
:
this
.
passArkoseLabsChallenge
,
onSuppress
:
this
.
passArkoseLabsChallenge
,
onError
:
this
.
handleArkoseLabsFailure
,
});
}
createArkoseLabsErrorMessageContainer
()
{
if
(
!
this
.
arkoseLabsErrorMessageContainer
)
{
const
arkoseLabsErrorMessageContainer
=
document
.
createElement
(
'
div
'
);
arkoseLabsErrorMessageContainer
.
className
=
`gl-mb-3
${
CHALLENGE_ERRORS_CONTAINER_CLASS
}
`
;
arkoseLabsErrorMessageContainer
.
setAttribute
(
'
data-testid
'
,
'
arkose-labs-error-message
'
);
this
.
arkoseLabsChallengeContainer
.
parentNode
.
insertBefore
(
arkoseLabsErrorMessageContainer
,
this
.
arkoseLabsChallengeContainer
.
nextSibling
,
);
this
.
arkoseLabsErrorMessageContainer
=
arkoseLabsErrorMessageContainer
;
}
this
.
arkoseLabsErrorMessageContainer
.
classList
.
remove
(
'
gl-display-none
'
);
}
showArkoseLabsErrorMessage
()
{
this
.
createArkoseLabsErrorMessageContainer
();
this
.
arkoseLabsErrorMessageContainer
.
innerHTML
=
`
<span class="gl-text-red-500">
${
__
(
'
Complete verification to sign in.
'
)}
</span>`
;
}
hideArkoseLabsErrorMessage
()
{
this
.
arkoseLabsErrorMessageContainer
?.
classList
.
add
(
'
gl-display-none
'
);
}
passArkoseLabsChallenge
(
response
)
{
this
.
arkoseLabsChallengePassed
=
true
;
this
.
tokenInput
.
value
=
response
.
token
;
this
.
hideArkoseLabsErrorMessage
();
}
handleArkoseLabsFailure
()
{
this
.
createArkoseLabsErrorMessageContainer
();
return
new
Vue
({
el
:
`.
${
CHALLENGE_ERRORS_CONTAINER_CLASS
}
`
,
components
:
{
GlAlert
},
render
(
h
)
{
return
h
(
GlAlert
,
{
props
:
{
title
:
__
(
'
Unable to verify the user
'
),
dismissible
:
false
,
variant
:
'
danger
'
,
},
attrs
:
{
'
data-testid
'
:
'
arkose-labs-failure-alert
'
,
},
},
__
(
'
An error occurred when loading the user verification challenge. Refresh to try again.
'
,
),
);
},
});
}
}
ee/app/assets/javascripts/arkose_labs/components/sign_in_arkose_app.vue
0 → 100644
View file @
33b095e4
<
script
>
import
{
uniqueId
}
from
'
lodash
'
;
import
{
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
needsArkoseLabsChallenge
}
from
'
ee/rest_api
'
;
import
{
loadingIconForLegacyJS
}
from
'
~/loading_icon_for_legacy_js
'
;
import
{
__
}
from
'
~/locale
'
;
import
DomElementListener
from
'
~/vue_shared/components/dom_element_listener.vue
'
;
import
{
initArkoseLabsScript
}
from
'
../init_arkose_labs_script
'
;
const
LOADING_ICON
=
loadingIconForLegacyJS
({
classes
:
[
'
gl-mr-2
'
]
});
const
MSG_ARKOSE_NEEDED
=
__
(
'
Complete verification to sign in.
'
);
const
MSG_ARKOSE_FAILURE_TITLE
=
__
(
'
Unable to verify the user
'
);
const
MSG_ARKOSE_FAILURE_BODY
=
__
(
'
An error occurred when loading the user verification challenge. Refresh to try again.
'
,
);
const
ARKOSE_CONTAINER_CLASS
=
'
js-arkose-labs-container-
'
;
const
VERIFICATION_TOKEN_INPUT_NAME
=
'
arkose_labs_token
'
;
export
default
{
components
:
{
DomElementListener
,
GlAlert
,
},
props
:
{
publicKey
:
{
type
:
String
,
required
:
true
,
},
formSelector
:
{
type
:
String
,
required
:
true
,
},
usernameSelector
:
{
type
:
String
,
required
:
true
,
},
submitSelector
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
isVisible
:
false
,
showArkoseNeededError
:
false
,
showArkoseFailure
:
false
,
username
:
''
,
isLoading
:
false
,
arkoseInitialized
:
false
,
arkoseToken
:
''
,
arkoseContainerClass
:
uniqueId
(
ARKOSE_CONTAINER_CLASS
),
arkoseChallengePassed
:
false
,
};
},
computed
:
{
showErrorContainer
()
{
return
this
.
showArkoseNeededError
||
this
.
showArkoseFailure
;
},
},
watch
:
{
username
()
{
this
.
checkIfNeedsChallenge
();
},
isLoading
(
val
)
{
this
.
updateSubmitButtonLoading
(
val
);
},
},
mounted
()
{
this
.
username
=
this
.
getUsernameValue
();
},
methods
:
{
show
()
{
this
.
isVisible
=
true
;
},
hideErrors
()
{
this
.
showArkoseNeededError
=
false
;
this
.
showArkoseFailure
=
false
;
},
getUsernameValue
()
{
return
document
.
querySelector
(
this
.
usernameSelector
)?.
value
||
''
;
},
onUsernameBlur
()
{
this
.
username
=
this
.
getUsernameValue
();
},
onSubmit
(
e
)
{
if
(
!
this
.
arkoseInitialized
||
this
.
arkoseChallengePassed
)
{
return
;
}
e
.
preventDefault
();
this
.
showArkoseNeededError
=
true
;
},
async
checkIfNeedsChallenge
()
{
if
(
!
this
.
username
||
this
.
arkoseInitialized
)
{
return
;
}
this
.
isLoading
=
true
;
try
{
const
{
data
:
{
result
},
}
=
await
needsArkoseLabsChallenge
(
this
.
username
);
if
(
result
)
{
this
.
arkoseInitialized
=
true
;
await
this
.
initArkoseLabs
();
}
}
catch
{
// API call failed, do not initialize Arkose challenge.
// Button will be reset in `finally` block.
// TODO - what if initArkoseLabs failed?
// TODO - Do we get any error objects we should console log?
}
finally
{
this
.
isLoading
=
false
;
}
},
async
initArkoseLabs
()
{
const
enforcement
=
await
initArkoseLabsScript
({
publicKey
:
this
.
publicKey
});
enforcement
.
setConfig
({
mode
:
'
inline
'
,
selector
:
`.
${
this
.
arkoseContainerClass
}
`
,
onShown
:
this
.
show
,
onCompleted
:
this
.
passArkoseLabsChallenge
,
onError
:
this
.
handleArkoseLabsFailure
,
});
},
passArkoseLabsChallenge
(
response
)
{
this
.
arkoseChallengePassed
=
true
;
this
.
arkoseToken
=
response
.
token
;
this
.
hideErrors
();
},
handleArkoseLabsFailure
()
{
// TODO - do we get an error object here we can console log?
this
.
showArkoseFailure
=
true
;
},
updateSubmitButtonLoading
(
val
)
{
const
button
=
document
.
querySelector
(
this
.
submitSelector
);
if
(
val
)
{
const
label
=
__
(
'
Loading
'
);
button
.
innerHTML
=
`
${
LOADING_ICON
.
outerHTML
}
${
label
}
`
;
button
.
setAttribute
(
'
disabled
'
,
true
);
}
else
{
button
.
innerText
=
__
(
'
Sign in
'
);
button
.
removeAttribute
(
'
disabled
'
);
}
},
},
MSG_ARKOSE_NEEDED
,
MSG_ARKOSE_FAILURE_TITLE
,
MSG_ARKOSE_FAILURE_BODY
,
VERIFICATION_TOKEN_INPUT_NAME
,
};
</
script
>
<
template
>
<div
v-show=
"isVisible"
>
<input
v-if=
"arkoseInitialized"
:name=
"$options.VERIFICATION_TOKEN_INPUT_NAME"
type=
"hidden"
:value=
"arkoseToken"
/>
<dom-element-listener
:selector=
"usernameSelector"
@
blur=
"onUsernameBlur"
/>
<dom-element-listener
:selector=
"formSelector"
@
submit=
"onSubmit"
/>
<div
class=
"gl-display-flex gl-justify-content-center gl-mt-3 gl-mb-n3"
:class=
"arkoseContainerClass"
data-testid=
"arkose-labs-challenge"
></div>
<div
v-if=
"showErrorContainer"
class=
"gl-mb-3"
data-testid=
"arkose-labs-error-message"
>
<gl-alert
v-if=
"showArkoseFailure"
:title=
"$options.MSG_ARKOSE_FAILURE_TITLE"
variant=
"danger"
:dismissible=
"false"
>
{{
$options
.
MSG_ARKOSE_FAILURE_BODY
}}
</gl-alert>
<span
v-else-if=
"showArkoseNeededError"
class=
"gl-text-red-500"
>
{{
$options
.
MSG_ARKOSE_NEEDED
}}
</span>
</div>
</div>
</
template
>
ee/app/assets/javascripts/arkose_labs/index.js
0 → 100644
View file @
33b095e4
import
Vue
from
'
vue
'
;
import
SignInArkoseApp
from
'
./components/sign_in_arkose_app.vue
'
;
const
FORM_SELECTOR
=
'
.js-sign-in-form
'
;
const
USERNAME_SELECTOR
=
`
${
FORM_SELECTOR
}
.js-username-field`
;
const
SUBMIT_SELECTOR
=
`
${
FORM_SELECTOR
}
.js-sign-in-button`
;
export
const
setupArkoseLabs
=
()
=>
{
const
signInForm
=
document
.
querySelector
(
FORM_SELECTOR
);
const
el
=
signInForm
?.
querySelector
(
'
.js-arkose-labs-challenge
'
);
if
(
!
el
)
{
return
null
;
}
const
publicKey
=
el
.
dataset
.
apiKey
;
return
new
Vue
({
el
,
render
(
h
)
{
return
h
(
SignInArkoseApp
,
{
props
:
{
publicKey
,
formSelector
:
FORM_SELECTOR
,
usernameSelector
:
USERNAME_SELECTOR
,
submitSelector
:
SUBMIT_SELECTOR
,
},
})
}
});
};
ee/app/assets/javascripts/arkose_labs/init_arkose_labs_script.js
0 → 100644
View file @
33b095e4
import
{
uniqueId
}
from
'
lodash
'
;
const
CALLBACK_NAME
=
'
_initArkoseLabsScript_callback_
'
;
const
getCallbackName
=
()
=>
uniqueId
(
CALLBACK_NAME
);
export
const
initArkoseLabsScript
=
({
publicKey
})
=>
{
const
callbackFunctionName
=
getCallbackName
();
return
new
Promise
((
resolve
)
=>
{
window
[
callbackFunctionName
]
=
(
enforcement
)
=>
{
delete
window
[
callbackFunctionName
];
resolve
(
enforcement
);
};
const
tag
=
document
.
createElement
(
'
script
'
);
[
[
'
type
'
,
'
text/javascript
'
],
[
'
src
'
,
`https://client-api.arkoselabs.com/v2/
${
publicKey
}
/api.js`
],
[
'
data-callback
'
,
callbackFunctionName
],
].
forEach
(([
attr
,
value
])
=>
{
tag
.
setAttribute
(
attr
,
value
);
});
document
.
head
.
appendChild
(
tag
);
});
};
ee/app/assets/javascripts/pages/sessions/new/index.js
View file @
33b095e4
import
'
~/pages/sessions/new/index
'
;
import
'
~/pages/sessions/new/index
'
;
if
(
gon
.
features
.
arkoseLabsLoginChallenge
)
{
if
(
gon
.
features
.
arkoseLabsLoginChallenge
)
{
import
(
'
ee/arkose_labs/arkose_labs
'
)
import
(
'
ee/arkose_labs
'
)
.
then
(({
ArkoseLabs
})
=>
{
.
then
(({
setupArkoseLabs
})
=>
{
// eslint-disable-next-line no-new
setupArkoseLabs
();
new
ArkoseLabs
();
})
})
.
catch
(()
=>
{});
.
catch
((
e
)
=>
{
throw
e
;
});
}
}
ee/app/views/devise/sessions/_arkose_labs.html.haml
View file @
33b095e4
.js-arkose-labs-challenge
.gl-display-flex.gl-justify-content-center.gl-mt-3.gl-mb-n3
{
class:
"gl-display-none!"
,
data:
{
api_key:
@arkose_labs_public_key
,
testid:
'arkose-labs-challenge'
}
}
.js-arkose-labs-challenge
{
data:
{
api_key:
@arkose_labs_public_key
}
}
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