-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add phone verification #12258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add phone verification #12258
Conversation
/werft run with-clean-slate-deployment=true |
a603f6a
to
7908ff0
Compare
/werft run with-clean-slate-deployment=true 👍 started the job as gitpod-build-sefftinge-add-phone-verification-11339.22 |
971d89a
to
a87f052
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some early feedback on the UX changes below. 🥯
visible={true} | ||
> | ||
<Alert type="warning" className="mt-2"> | ||
To use Gitpod you'll need to validate your account with your phone number. This is required to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: Does it help us achieve our goal (abuse)? I'd rather go with a neutral tone but any option could suffice. Feel free to ignore this nitpick. 🙂
To use Gitpod you'll need to validate your account with your phone number. This is required to | |
To use Gitpod you'll need to validate your account with a mobile phone number. This is required to |
0924951
to
6ecdbfa
Compare
Looking at this now! 👀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@svenefftinge I've left another round of comments. 🥊
I could not go through all the steps of the flow. 😭
6ecdbfa
to
a3bcdf3
Compare
Looking at this now! 👀 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright! Left a few general comments and questions below. 🔮
Most of them can be ignored or used for opening follow up issues out of the scope of these changes. 🛹
Thanks @svenefftinge for pushing this forward! 🏀
visible={true} | ||
> | ||
<Alert type="message" className="mt-2"> | ||
Your account has been successfully verified. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Now that we've tried this, we probably need a new alert component variant for successful messages like this that is using green-ish colors. Could be fine to leave this out of the scope of these changes for now. Your call.
Alert / Info (IMPLEMENTED) | Alert / Success (NOT IMPLEMENTED) | Alert / Success (NOT IMPLEMENTED) 🌔 |
---|---|---|
![]() |
![]() |
![]() |
If you'd like to add this here, here's the icon (SVG) and the colors used for light and dark themes:
LIGHT THEME ⛅
Background:
green-100
Icon:green-500
Text:green-700
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18ZM13.7071 8.70711C14.0976 8.31658 14.0976 7.68342 13.7071 7.29289C13.3166 6.90237 12.6834 6.90237 12.2929 7.29289L9 10.5858L7.70711 9.29289C7.31658 8.90237 6.68342 8.90237 6.29289 9.29289C5.90237 9.68342 5.90237 10.3166 6.29289 10.7071L8.29289 12.7071C8.68342 13.0976 9.31658 13.0976 9.70711 12.7071L13.7071 8.70711Z" fill="#84CC16"/>
</svg>
DARK THEME 🌔
Background:
green-800
Icon:green-50
Text:green-100
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18ZM13.7071 8.70711C14.0976 8.31658 14.0976 7.68342 13.7071 7.29289C13.3166 6.90237 12.6834 6.90237 12.2929 7.29289L9 10.5858L7.70711 9.29289C7.31658 8.90237 6.68342 8.90237 6.29289 9.29289C5.90237 9.68342 5.90237 10.3166 6.29289 10.7071L8.29289 12.7071C8.68342 13.0976 9.31658 13.0976 9.70711 12.7071L13.7071 8.70711Z" fill="#F7FEE7"/>
</svg>
onClose={continueStartWorkspace} | ||
closeable={false} | ||
onEnter={continueStartWorkspace} | ||
title="User Validation Successful" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Given it's not ideal to update the modal title based on the user action and taking into account the relevant discussion (internal), what do you think of moving the alert component on the loading screen directly where we later on show the warnings about the latest editor release and skip asking users to acknowledge the account validation?
Latest Release on Workspace Start | Validation on Workspace Start |
---|---|
![]() |
![]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think an explicit, "you are verified" message with a 'continue' button is clearer. Especially because we are reloading the page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thought: It is certainly clearer and gives users the time to acknowledge this. 💯
question: What do you think of keeping modal, and updating the modal title to be the same on all cases, and rely on modal body (paragraph and alerts) to inform the user about validation errors or successful validation?
b6d31bc
to
8fa474f
Compare
I'll hide this behind a feature flag so we can carefully enable it in prod. |
8fa474f
to
979777c
Compare
/werft run 👍 started the job as gitpod-build-sefftinge-add-phone-verification-11339.33 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that this PR does not have changes in Team Self-Hosted owned files anymore. However, from self-hosted perspective, it looks good so far.
main
already that will let fail the main
build after merging. Sorry for the inconvenience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The account verification works nicely 🎉
I left some comment, and I'm happy to discuss the additionalData
comment 👍🏻
Ah, the only real issue so far is that old GH account are not count as verified, because the check is not performed after signup, but rather after the following re-login.
@@ -51,6 +51,8 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig { | |||
getLoggedInUser: { group: "default", points: 1 }, | |||
getTerms: { group: "default", points: 1 }, | |||
updateLoggedInUser: { group: "default", points: 1 }, | |||
sendPhoneNumberVerificationToken: { group: "default", points: 1 }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this mean it would be allowed to call it full 1k per interval?
if the external service imposes a rate limit on our registration, we'd be exposed to random DoS attacks, no?
in that case let's introduce a config with a reasonable limitation of several tries per 10s – similar to create workspace and the like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this also applies to verifyPhoneNumberVerificationToken
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes. Probably worth limiting this per user on our side, although there is a rate limit per phone number on twilio as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm introducing a new category phoneVerification
with 10 points per 10 seconds.
@@ -66,6 +66,9 @@ export class UserDeletionService { | |||
this.deleteIdentities(user); | |||
await this.deleteTokens(db, user); | |||
user.markedDeleted = true; | |||
if (user.additionalData) { | |||
delete user.additionalData.lastVerificationTime; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we'd need to delete the phone number as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I intentionally left it in order to follow up in case the user did something bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That we definitely need to check with data privacy. To prevent recreating accounts it would be sufficient to store a hash of the phone number, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll store the hash instead and introduce d_b_blocked_phone_hashes
that we update on user block/unblock. Also will check that table on first sign up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: I'm leaving the blocked_phone_hashes out of this PR, as it makes it even bigger (it already is big).
But I'm moving the two properties into d_b_user
.
@@ -419,6 +422,11 @@ export class UserService { | |||
user.name = user.name || authUser.name || authUser.primaryEmail; | |||
user.avatarUrl = user.avatarUrl || authUser.avatarUrl; | |||
|
|||
if (authUser.created_at && isDateSmaller(authUser.created_at, daysBefore(new Date().toISOString(), 30))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updateUserOnLogin
is not called on signups. that means, a new Gitpod account for an old GH account is not considered trusted.
for a complete picture, I think this should not be updated on logins (but rather on signups,) otherwise they might just create a pile of GH account and start using them after a while.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this check to createUser
, that should fix Test 2
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved it to the callback that is passed to createUser
because I needed access to the authUser object. Also looked like a good pace for it otherwise. Fine?
for (const p of allowedFields) { | ||
if (p in partialUser) { | ||
(user[p] as any) = partialUser[p]; | ||
} | ||
} | ||
// special treatment of nested additionalData | ||
if (partialUser.additionalData) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preferably, let's not use a field dedicated for user preferences/settings for something that actually controls the wheels or breaks. That check seems to work fine, I've tried to break without success, but I'd prefer not to have a this guarded places in a field which otherwise is free to change for users.
There is an option that we can use to add field to tables without the downside of locking tables, we just need to add NOT NULL DEFAULT '', ALGORITHM=INPLACE, LOCK=NONE
for the alter statement.
As we used this alternative the last time(s) discussed this matter, I'd prefer to stick to one pattern 🙏🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which discussion do you refer to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid implementing the checks for nested attributes in the additionalData
bucket we've moved the attribution override to User
in #11522. The validation necessary is to disallow users setting the attribution target to a team id they are not a member of.
|
||
async mayStartWorkspace( | ||
user: User, | ||
date: Date = new Date(), | ||
runningInstances: Promise<WorkspaceInstance[]>, | ||
): Promise<MayStartWorkspaceResult> { | ||
try { | ||
const verification = await this.verificationService.needsVerification(user); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice integration! Fits in perfectly.
That reminds me of the ToS flow integration, which intercepted the signup, but it would actually be better to use this kind of hook.
if (!this.verifyService) { | ||
throw new Error("No verification service configured."); | ||
} | ||
const verification_check = await this.verifyService.verificationChecks.create({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try-catch, otherwise it's handled as internal server error whenever one uses the happy finger on the send button.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get this comment. Why is there always an internal server error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as this is not wrapped in a ResponseError
it'll be logged automatically as an internal server error, thus it's loud on the logs, and it seems not to be rendered properly on frontend, at least I couldn't see twillios rate limiting error rendered on frontend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is fine to be loud on the logs. It's not a user error when an error is thrown but something with the system. The error is thrown to frontend and I'll make it so that the user gets feedback about the error.
👋 @svenefftinge, would it be possible to include bitbucket with this too, or perhaps in a follow-on PR? I ask because of this thread. |
The logic is opt-out. So all bitbucket users need to verify. |
8e00464
to
3dc9ac6
Compare
/werft run 👍 started the job as gitpod-build-sefftinge-add-phone-verification-11339.37 |
/werft run with-clean-slate-deployment=true 👍 started the job as gitpod-build-sefftinge-add-phone-verification-11339.38 |
/werft run with-clean-slate-deployment=true 👍 started the job as gitpod-build-sefftinge-add-phone-verification-11339.40 |
@@ -148,6 +149,9 @@ export class UserDeletionService { | |||
user.avatarUrl = "deleted-avatarUrl"; | |||
user.fullName = "deleted-fullName"; | |||
user.name = "deleted-Name"; | |||
if (user.verificationPhoneNumber) { | |||
user.verificationPhoneNumber = "deleted-verificationPhoneNumber"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ER_DATA_TOO_LONG: Data too long for column 'verificationPhoneNumber'
we're almost there, just 2 chars too long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what did you type in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, got it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That happens on deletion of an account, which actually breaks the process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deleted-phoneNumber
with a length of 20 should work just fine.
3dc9ac6
to
1044b42
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Tested works as advertised.
Logic for Test 1 ++
isn't there is code, which I think is not needed at all ;-)
Description
This PR adds phone number verification before untrusted users can start a workspace.
A user is considered trusted if
confirmed_at
event is older than 30 days, orRelated Issue(s)
Fixes #11339
See also https://github.com/gitpod-io/ops/pull/4149
How to test
Test 1
Test 2
Test 1 ++
lastVerificationTimestamp
in your user's additional Data.Release Notes
Documentation
Werft options: