2021-12-25 02:16:58 +01:00
package server
import (
"encoding/json"
"fmt"
2022-07-31 18:59:11 +02:00
"log"
2021-12-25 02:16:58 +01:00
"net/http"
2022-07-13 18:32:48 +02:00
2022-08-22 18:05:53 +02:00
"lbryio/wallet-sync-server/auth"
"lbryio/wallet-sync-server/env"
"lbryio/wallet-sync-server/store"
2021-12-25 02:16:58 +01:00
)
type RegisterRequest struct {
2022-07-15 21:36:11 +02:00
Email auth . Email ` json:"email" `
Password auth . Password ` json:"password" `
ClientSaltSeed auth . ClientSaltSeed ` json:"clientSaltSeed" `
2021-12-25 02:16:58 +01:00
}
2022-07-26 16:16:44 +02:00
type RegisterResponse struct {
Verified bool ` json:"verified" `
}
2022-07-11 15:42:08 +02:00
func ( r * RegisterRequest ) validate ( ) error {
2022-07-24 22:02:55 +02:00
if ! r . Email . Validate ( ) {
2022-07-15 21:36:11 +02:00
return fmt . Errorf ( "Invalid or missing 'email'" )
2022-07-11 15:42:08 +02:00
}
2022-08-23 01:41:30 +02:00
if ! r . Password . Validate ( ) {
return fmt . Errorf ( "Invalid or missing 'password'" )
2022-07-11 15:42:08 +02:00
}
2022-07-24 22:02:55 +02:00
if ! r . ClientSaltSeed . Validate ( ) {
2022-07-15 21:36:11 +02:00
return fmt . Errorf ( "Invalid or missing 'clientSaltSeed'" )
}
2022-07-11 15:42:08 +02:00
return nil
2021-12-25 02:16:58 +01:00
}
func ( s * Server ) register ( w http . ResponseWriter , req * http . Request ) {
var registerRequest RegisterRequest
if ! getPostData ( w , req , & registerRequest ) {
return
}
2022-07-26 16:16:44 +02:00
verificationMode , err := env . GetAccountVerificationMode ( s . env )
if err != nil {
internalServiceErrorJson ( w , err , "Error getting account verification mode" )
return
}
accountWhitelist , err := env . GetAccountWhitelist ( s . env , verificationMode )
if err != nil {
internalServiceErrorJson ( w , err , "Error getting account whitelist" )
return
}
var registerResponse RegisterResponse
2022-08-14 04:15:19 +02:00
var token * auth . VerifyTokenString
2022-07-28 01:45:09 +02:00
2022-07-26 16:16:44 +02:00
modes :
switch verificationMode {
case env . AccountVerificationModeAllowAll :
// Always verified (for testers). No need to jump through email verify
// hoops.
registerResponse . Verified = true
case env . AccountVerificationModeWhitelist :
for _ , whitelisteEmail := range accountWhitelist {
if whitelisteEmail == registerRequest . Email {
registerResponse . Verified = true
break modes
}
}
// If we have unverified users on whitelist setups, we'd need to create a way
// to verify them. It's easier to just prevent account creation. It also will
// make it easier for self-hosters to figure out that something is wrong
// with their whitelist.
errorJson ( w , http . StatusForbidden , "Account not whitelisted" )
return
case env . AccountVerificationModeEmailVerify :
// Not verified until they click their email link.
registerResponse . Verified = false
2022-08-14 04:15:19 +02:00
newToken , err := s . auth . NewVerifyTokenString ( )
token = & newToken
2022-07-28 01:45:09 +02:00
if err != nil {
internalServiceErrorJson ( w , err , "Error generating verify token string" )
return
}
2022-07-26 16:16:44 +02:00
}
err = s . store . CreateAccount (
registerRequest . Email ,
registerRequest . Password ,
registerRequest . ClientSaltSeed ,
2022-07-28 01:45:09 +02:00
token , // if it's not set, the user is marked as verified
2022-07-26 16:16:44 +02:00
)
2021-12-25 02:16:58 +01:00
if err != nil {
if err == store . ErrDuplicateEmail || err == store . ErrDuplicateAccount {
2022-06-07 19:25:14 +02:00
errorJson ( w , http . StatusConflict , "Error registering" )
2021-12-25 02:16:58 +01:00
} else {
2022-06-07 19:25:14 +02:00
internalServiceErrorJson ( w , err , "Error registering" )
2021-12-25 02:16:58 +01:00
}
return
}
2022-08-14 04:15:19 +02:00
if token != nil {
err = s . mail . SendVerificationEmail ( registerRequest . Email , * token )
2022-07-28 01:45:09 +02:00
}
if err != nil {
internalServiceErrorJson ( w , err , "Error sending verification email" )
return
}
2022-07-26 16:16:44 +02:00
response , err := json . Marshal ( registerResponse )
2021-12-25 02:16:58 +01:00
if err != nil {
2022-06-07 19:25:14 +02:00
internalServiceErrorJson ( w , err , "Error generating register response" )
2021-12-25 02:16:58 +01:00
return
}
2022-06-09 23:04:49 +02:00
// TODO StatusCreated also for first wallet and/or for get auth token?
2021-12-25 02:16:58 +01:00
w . WriteHeader ( http . StatusCreated )
fmt . Fprintf ( w , string ( response ) )
2022-08-25 18:42:49 +02:00
log . Printf ( "User %s has registered" , registerRequest . Email )
2021-12-25 02:16:58 +01:00
}
2022-07-26 22:36:57 +02:00
// TODO - There's probably a struct-based solution here like with POST/PUT.
// We could put that struct up top as well.
func getVerifyParams ( req * http . Request ) ( token auth . VerifyTokenString , err error ) {
tokenSlice , hasTokenSlice := req . URL . Query ( ) [ "verifyToken" ]
if ! hasTokenSlice || tokenSlice [ 0 ] == "" {
err = fmt . Errorf ( "Missing verifyToken parameter" )
}
if err == nil {
token = auth . VerifyTokenString ( tokenSlice [ 0 ] )
}
return
}
2022-07-30 02:49:00 +02:00
type ResendVerifyEmailRequest struct {
Email auth . Email ` json:"email" `
}
func ( r * ResendVerifyEmailRequest ) validate ( ) error {
if ! r . Email . Validate ( ) {
return fmt . Errorf ( "Invalid or missing 'email'" )
}
return nil
}
func ( s * Server ) resendVerifyEmail ( w http . ResponseWriter , req * http . Request ) {
verificationMode , err := env . GetAccountVerificationMode ( s . env )
if err != nil {
internalServiceErrorJson ( w , err , "Error getting account verification mode" )
return
}
if verificationMode != env . AccountVerificationModeEmailVerify {
errorJson ( w , http . StatusForbidden , "Account verification mode is not set to EmailVerify" )
return
}
var resendVerifyEmailRequest ResendVerifyEmailRequest
if ! getPostData ( w , req , & resendVerifyEmailRequest ) {
return
}
token , err := s . auth . NewVerifyTokenString ( )
if err != nil {
internalServiceErrorJson ( w , err , "Error generating verify token string" )
return
}
err = s . store . UpdateVerifyTokenString ( resendVerifyEmailRequest . Email , token )
if err == store . ErrWrongCredentials {
errorJson ( w , http . StatusUnauthorized , "No match for email" )
return
}
if err != nil {
internalServiceErrorJson ( w , err , "Error updating verify token string" )
return
}
err = s . mail . SendVerificationEmail ( resendVerifyEmailRequest . Email , token )
if err != nil {
internalServiceErrorJson ( w , err , "Error re-sending verification email" )
return
}
var verifyResponse struct { }
response , err := json . Marshal ( verifyResponse )
if err != nil {
internalServiceErrorJson ( w , err , "Error generating verify response" )
return
}
fmt . Fprintf ( w , string ( response ) )
}
2022-07-26 22:36:57 +02:00
func ( s * Server ) verify ( w http . ResponseWriter , req * http . Request ) {
if ! getGetData ( w , req ) {
return
}
token , paramsErr := getVerifyParams ( req )
if paramsErr != nil {
// In this specific case, the error is limited to values that are safe to
// give to the user.
2022-07-31 18:59:11 +02:00
http . Error ( w , "There seems to be a problem with this URL: " + paramsErr . Error ( ) , http . StatusBadRequest )
2022-07-26 22:36:57 +02:00
return
}
err := s . store . VerifyAccount ( token )
if err == store . ErrNoTokenForUser {
2022-08-01 17:50:16 +02:00
http . Error ( w , "The verification token was not found, already used, or expired. If you want to try again, generate a new one from your app." , http . StatusForbidden )
2022-07-26 22:36:57 +02:00
return
} else if err != nil {
2022-07-31 18:59:11 +02:00
http . Error ( w , "Something went wrong trying to verify your account." , http . StatusInternalServerError )
log . Printf ( "%s: %+v\n" , "Error verifying account" , err )
2022-07-26 22:36:57 +02:00
return
}
2022-07-31 18:59:11 +02:00
fmt . Fprintf ( w , "Your account has been verified." )
2022-08-25 18:42:49 +02:00
// if we really want to log the user's email at some point
// we can put in the effort then to fetch it
log . Printf ( "User has been verified with token %s" , token )
2022-07-26 22:36:57 +02:00
}