diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ccda182ec..98717322eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,11 +16,16 @@ The format is based on [keep a changelog](http://keepachangelog.com/) and this p - Streamline command line flags to be inline with the config file. - Restructure and stabilize API messages. - Update Runtime modules to use plural function names for batch operations. (`users_fetch_id` and `users_fetch_handle`) +- Script runtime JSON encoder/decoder now support non-object JSON documents. +- Script runtime storage bindings now expect and return Lua tables for values. +- Attempting to login with an ID that does not exist will return a new dedicated error code. +- Attempting to register with an ID that already exists will return a new dedicated error code. ### Fixed - Invocation type was always set to "Before" in After Runtime scripts. - User ID was not passed to context in After Authentication invocations. - Authentication runtime invocation messages were named with leading "." and trailing "_". +- Attempting to link a device ID that is already in use will return the correct "link in use" error code. ## [0.13.1] - 2017-06-08 ### Added diff --git a/server/api.proto b/server/api.proto index 6f9935b8d7..fcdf011f12 100644 --- a/server/api.proto +++ b/server/api.proto @@ -52,24 +52,28 @@ message Error { BAD_INPUT = 3; /// Authentication failure. AUTH_ERROR = 4; + /// Login failed because ID/device/email did not exist. + USER_NOT_FOUND = 5; + /// Registration failed because ID/device/email exists. + USER_REGISTER_INUSE = 6; /// Linking operation failed because link exists. - USER_LINK_INUSE = 5; + USER_LINK_INUSE = 7; /// Linking operation failed because third-party service was unreachable. - USER_LINK_PROVIDER_UNAVAILABLE = 6; + USER_LINK_PROVIDER_UNAVAILABLE = 8; /// Unlinking operation failed because you cannot unlink last ID. - USER_UNLINK_DISALLOWED = 7; + USER_UNLINK_DISALLOWED = 9; /// Handle is in-use by another user. - USER_HANDLE_INUSE = 8; + USER_HANDLE_INUSE = 10; /// Group names must be unique and it's already in use. - GROUP_NAME_INUSE = 9; + GROUP_NAME_INUSE = 11; /// Storage write operation failed. - STORAGE_REJECTED = 10; + STORAGE_REJECTED = 12; /// Match with given ID was not found in the system. - MATCH_NOT_FOUND = 11; + MATCH_NOT_FOUND = 13; /// Runtime function name was not found in system registry. - RUNTIME_FUNCTION_NOT_FOUND = 12; + RUNTIME_FUNCTION_NOT_FOUND = 14; /// Runtime function caused an internal server error and did not complete. - RUNTIME_FUNCTION_EXCEPTION = 13; + RUNTIME_FUNCTION_EXCEPTION = 15; } /// Error code - must be one of the Error.Code enums above. diff --git a/server/pipeline_link_unlink.go b/server/pipeline_link_unlink.go index 559cdd71d1..95f3c717fb 100644 --- a/server/pipeline_link_unlink.go +++ b/server/pipeline_link_unlink.go @@ -68,12 +68,17 @@ func (p *pipeline) linkDevice(logger *zap.Logger, session *session, envelope *En } res, err := txn.Exec("INSERT INTO user_device (id, user_id) VALUES ($1, $2)", deviceID, session.userID.Bytes()) if err != nil { - logger.Warn("Could not link, query error", zap.Error(err)) - err = txn.Rollback() - if err != nil { - logger.Warn("Could not link, transaction rollback error", zap.Error(err)) + // In any error case the link has failed, so we can rollback before checking what went wrong. + if e := txn.Rollback(); e != nil { + logger.Warn("Could not link, transaction rollback error", zap.Error(e)) + } + + if strings.HasSuffix(err.Error(), "violates unique constraint \"primary\"") { + session.Send(ErrorMessage(envelope.CollationId, USER_LINK_INUSE, "Device ID in use")) + } else { + logger.Warn("Could not link, query error", zap.Error(err)) + session.Send(ErrorMessageRuntimeException(envelope.CollationId, "Could not link")) } - session.Send(ErrorMessageRuntimeException(envelope.CollationId, "Could not link")) return } if count, _ := res.RowsAffected(); count == 0 { @@ -104,7 +109,7 @@ func (p *pipeline) linkDevice(logger *zap.Logger, session *session, envelope *En } err = txn.Commit() if err != nil { - logger.Warn("Could not register, transaction commit error", zap.Error(err)) + logger.Warn("Could not link, transaction commit error", zap.Error(err)) session.Send(ErrorMessageRuntimeException(envelope.CollationId, "Could not link")) return } diff --git a/server/session_auth.go b/server/session_auth.go index 094341fad0..0bf11ac730 100644 --- a/server/session_auth.go +++ b/server/session_auth.go @@ -242,23 +242,23 @@ func (a *authenticationService) StartServer(logger *zap.Logger) { } func (a *authenticationService) handleAuth(w http.ResponseWriter, r *http.Request, - retrieveUserID func(authReq *AuthenticateRequest) ([]byte, string, string, int)) { + retrieveUserID func(authReq *AuthenticateRequest) ([]byte, string, string, Error_Code)) { w.Header().Set("Content-Type", "application/octet-stream") username, _, ok := r.BasicAuth() if !ok { - a.sendAuthError(w, r, "Missing or invalid authentication header", 400, nil) + a.sendAuthError(w, r, "Missing or invalid authentication header", AUTH_ERROR, nil) return } else if username != a.config.GetTransport().ServerKey { - a.sendAuthError(w, r, "Invalid server key", 401, nil) + a.sendAuthError(w, r, "Invalid server key", AUTH_ERROR, nil) return } data, err := ioutil.ReadAll(http.MaxBytesReader(w, r.Body, a.config.GetTransport().MaxMessageSizeBytes)) if err != nil { a.logger.Warn("Could not read body", zap.Error(err)) - a.sendAuthError(w, r, "Could not read request body", 400, nil) + a.sendAuthError(w, r, "Could not read request body", AUTH_ERROR, nil) return } @@ -269,7 +269,7 @@ func (a *authenticationService) handleAuth(w http.ResponseWriter, r *http.Reques mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { a.logger.Warn("Could not decode content type header", zap.Error(err)) - a.sendAuthError(w, r, "Could not decode content type header", 400, nil) + a.sendAuthError(w, r, "Could not decode content type header", AUTH_ERROR, nil) return } @@ -282,7 +282,7 @@ func (a *authenticationService) handleAuth(w http.ResponseWriter, r *http.Reques } if err != nil { a.logger.Warn("Could not decode body", zap.Error(err)) - a.sendAuthError(w, r, "Could not decode body", 400, nil) + a.sendAuthError(w, r, "Could not decode body", AUTH_ERROR, nil) return } @@ -291,13 +291,13 @@ func (a *authenticationService) handleAuth(w http.ResponseWriter, r *http.Reques authReq, fnErr := RuntimeBeforeHookAuthentication(a.runtime, a.jsonpbMarshaler, a.jsonpbUnmarshaler, authReq) if fnErr != nil { a.logger.Error("Runtime before function caused an error", zap.String("message", messageType), zap.Error(fnErr)) - a.sendAuthError(w, r, "Runtime before function caused an error", 500, authReq) + a.sendAuthError(w, r, "Runtime before function caused an error", RUNTIME_FUNCTION_EXCEPTION, authReq) return } userID, handle, errString, errCode := retrieveUserID(authReq) if errString != "" { - a.logger.Debug("Could not retrieve user ID", zap.String("error", errString), zap.Int("code", errCode)) + a.logger.Debug("Could not retrieve user ID", zap.String("error", errString), zap.Int("code", int(errCode))) a.sendAuthError(w, r, errString, errCode, authReq) return } @@ -318,17 +318,34 @@ func (a *authenticationService) handleAuth(w http.ResponseWriter, r *http.Reques RuntimeAfterHookAuthentication(a.logger, a.runtime, a.jsonpbMarshaler, authReq, uid, handle, exp) } -func (a *authenticationService) sendAuthError(w http.ResponseWriter, r *http.Request, error string, errorCode int, authRequest *AuthenticateRequest) { +func (a *authenticationService) sendAuthError(w http.ResponseWriter, r *http.Request, error string, errorCode Error_Code, authRequest *AuthenticateRequest) { var collationID string if authRequest != nil { collationID = authRequest.CollationId } authResponse := &AuthenticateResponse{CollationId: collationID, Id: &AuthenticateResponse_Error_{&AuthenticateResponse_Error{ - Code: int32(AUTH_ERROR), + Code: int32(errorCode), Message: error, Request: authRequest, }}} - a.sendAuthResponse(w, r, errorCode, authResponse) + httpCode := 500 + switch errorCode { + case RUNTIME_EXCEPTION: + httpCode = 500 + case AUTH_ERROR: + httpCode = 401 + case RUNTIME_FUNCTION_EXCEPTION: + httpCode = 500 + case BAD_INPUT: + httpCode = 400 + case USER_NOT_FOUND: + httpCode = 401 + case USER_REGISTER_INUSE: + httpCode = 401 + default: + httpCode = 500 + } + a.sendAuthResponse(w, r, httpCode, authResponse) } func (a *authenticationService) sendAuthResponse(w http.ResponseWriter, r *http.Request, code int, response *AuthenticateResponse) { @@ -363,9 +380,9 @@ func (a *authenticationService) sendAuthResponse(w http.ResponseWriter, r *http. w.Write(payload) } -func (a *authenticationService) login(authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) login(authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { // Route to correct login handler - var loginFunc func(authReq *AuthenticateRequest) ([]byte, string, int64, string, int) + var loginFunc func(authReq *AuthenticateRequest) ([]byte, string, int64, string, Error_Code) switch authReq.Id.(type) { case *AuthenticateRequest_Device: loginFunc = a.loginDevice @@ -382,26 +399,26 @@ func (a *authenticationService) login(authReq *AuthenticateRequest) ([]byte, str case *AuthenticateRequest_Custom: loginFunc = a.loginCustom default: - return nil, "", errorInvalidPayload, 400 + return nil, "", errorInvalidPayload, BAD_INPUT } - userID, handle, disabledAt, message, status := loginFunc(authReq) + userID, handle, disabledAt, message, errorCode := loginFunc(authReq) if disabledAt != 0 { - return nil, "", "ID disabled", 401 + return nil, "", "ID disabled", AUTH_ERROR } - return userID, handle, message, status + return userID, handle, message, errorCode } -func (a *authenticationService) loginDevice(authReq *AuthenticateRequest) ([]byte, string, int64, string, int) { +func (a *authenticationService) loginDevice(authReq *AuthenticateRequest) ([]byte, string, int64, string, Error_Code) { deviceID := authReq.GetDevice() if deviceID == "" { - return nil, "", 0, "Device ID is required", 400 + return nil, "", 0, "Device ID is required", BAD_INPUT } else if invalidCharsRegex.MatchString(deviceID) { - return nil, "", 0, "Invalid device ID, no spaces or control characters allowed", 400 + return nil, "", 0, "Invalid device ID, no spaces or control characters allowed", BAD_INPUT } else if len(deviceID) < 10 || len(deviceID) > 64 { - return nil, "", 0, "Invalid device ID, must be 10-64 bytes", 400 + return nil, "", 0, "Invalid device ID, must be 10-64 bytes", BAD_INPUT } var userID []byte @@ -411,25 +428,29 @@ func (a *authenticationService) loginDevice(authReq *AuthenticateRequest) ([]byt deviceID). Scan(&userID, &handle, &disabledAt) if err != nil { - a.logger.Warn(errorCouldNotLogin, zap.Error(err)) - return nil, "", 0, errorIDNotFound, 401 + if err == sql.ErrNoRows { + return nil, "", 0, errorIDNotFound, USER_NOT_FOUND + } else { + a.logger.Warn(errorCouldNotLogin, zap.String("profile", "device"), zap.Error(err)) + return nil, "", 0, errorCouldNotLogin, RUNTIME_EXCEPTION + } } - return userID, handle, disabledAt, "", 200 + return userID, handle, disabledAt, "", 0 } -func (a *authenticationService) loginFacebook(authReq *AuthenticateRequest) ([]byte, string, int64, string, int) { +func (a *authenticationService) loginFacebook(authReq *AuthenticateRequest) ([]byte, string, int64, string, Error_Code) { accessToken := authReq.GetFacebook() if accessToken == "" { - return nil, "", 0, errorAccessTokenIsRequired, 400 + return nil, "", 0, errorAccessTokenIsRequired, BAD_INPUT } else if invalidCharsRegex.MatchString(accessToken) { - return nil, "", 0, "Invalid Facebook access token, no spaces or control characters allowed", 400 + return nil, "", 0, "Invalid Facebook access token, no spaces or control characters allowed", BAD_INPUT } fbProfile, err := a.socialClient.GetFacebookProfile(accessToken) if err != nil { a.logger.Warn("Could not get Facebook profile", zap.Error(err)) - return nil, "", 0, errorCouldNotLogin, 401 + return nil, "", 0, errorCouldNotLogin, AUTH_ERROR } var userID []byte @@ -439,25 +460,29 @@ func (a *authenticationService) loginFacebook(authReq *AuthenticateRequest) ([]b fbProfile.ID). Scan(&userID, &handle, &disabledAt) if err != nil { - a.logger.Warn("Could not login with Facebook profile", zap.Error(err)) - return nil, "", 0, errorIDNotFound, 401 + if err == sql.ErrNoRows { + return nil, "", 0, errorIDNotFound, USER_NOT_FOUND + } else { + a.logger.Warn(errorCouldNotLogin, zap.String("profile", "facebook"), zap.Error(err)) + return nil, "", 0, errorCouldNotLogin, RUNTIME_EXCEPTION + } } - return userID, handle, disabledAt, "", 200 + return userID, handle, disabledAt, "", 0 } -func (a *authenticationService) loginGoogle(authReq *AuthenticateRequest) ([]byte, string, int64, string, int) { +func (a *authenticationService) loginGoogle(authReq *AuthenticateRequest) ([]byte, string, int64, string, Error_Code) { accessToken := authReq.GetGoogle() if accessToken == "" { - return nil, "", 0, errorAccessTokenIsRequired, 400 + return nil, "", 0, errorAccessTokenIsRequired, BAD_INPUT } else if invalidCharsRegex.MatchString(accessToken) { - return nil, "", 0, "Invalid Google access token, no spaces or control characters allowed", 400 + return nil, "", 0, "Invalid Google access token, no spaces or control characters allowed", BAD_INPUT } googleProfile, err := a.socialClient.GetGoogleProfile(accessToken) if err != nil { a.logger.Warn("Could not get Google profile", zap.Error(err)) - return nil, "", 0, errorCouldNotLogin, 401 + return nil, "", 0, errorCouldNotLogin, AUTH_ERROR } var userID []byte @@ -467,23 +492,27 @@ func (a *authenticationService) loginGoogle(authReq *AuthenticateRequest) ([]byt googleProfile.ID). Scan(&userID, &handle, &disabledAt) if err != nil { - a.logger.Warn("Could not login with Google profile", zap.Error(err)) - return nil, "", 0, errorIDNotFound, 401 + if err == sql.ErrNoRows { + return nil, "", 0, errorIDNotFound, USER_NOT_FOUND + } else { + a.logger.Warn(errorCouldNotLogin, zap.String("profile", "google"), zap.Error(err)) + return nil, "", 0, errorCouldNotLogin, RUNTIME_EXCEPTION + } } - return userID, handle, disabledAt, "", 200 + return userID, handle, disabledAt, "", 0 } -func (a *authenticationService) loginGameCenter(authReq *AuthenticateRequest) ([]byte, string, int64, string, int) { +func (a *authenticationService) loginGameCenter(authReq *AuthenticateRequest) ([]byte, string, int64, string, Error_Code) { gc := authReq.GetGameCenter() if gc == nil || gc.PlayerId == "" || gc.BundleId == "" || gc.Timestamp == 0 || gc.Salt == "" || gc.Signature == "" || gc.PublicKeyUrl == "" { - return nil, "", 0, errorInvalidPayload, 400 + return nil, "", 0, errorInvalidPayload, BAD_INPUT } _, err := a.socialClient.CheckGameCenterID(gc.PlayerId, gc.BundleId, gc.Timestamp, gc.Salt, gc.Signature, gc.PublicKeyUrl) if err != nil { a.logger.Warn("Could not check Game Center profile", zap.Error(err)) - return nil, "", 0, errorCouldNotLogin, 401 + return nil, "", 0, errorCouldNotLogin, AUTH_ERROR } var userID []byte @@ -493,29 +522,33 @@ func (a *authenticationService) loginGameCenter(authReq *AuthenticateRequest) ([ gc.PlayerId). Scan(&userID, &handle, &disabledAt) if err != nil { - a.logger.Warn("Could not login with Game Center profile", zap.Error(err)) - return nil, "", 0, errorIDNotFound, 401 + if err == sql.ErrNoRows { + return nil, "", 0, errorIDNotFound, USER_NOT_FOUND + } else { + a.logger.Warn(errorCouldNotLogin, zap.String("profile", "game center"), zap.Error(err)) + return nil, "", 0, errorCouldNotLogin, RUNTIME_EXCEPTION + } } - return userID, handle, disabledAt, "", 200 + return userID, handle, disabledAt, "", 0 } -func (a *authenticationService) loginSteam(authReq *AuthenticateRequest) ([]byte, string, int64, string, int) { +func (a *authenticationService) loginSteam(authReq *AuthenticateRequest) ([]byte, string, int64, string, Error_Code) { if a.config.GetSocial().Steam.PublisherKey == "" || a.config.GetSocial().Steam.AppID == 0 { - return nil, "", 0, "Steam login not available", 401 + return nil, "", 0, "Steam login not available", AUTH_ERROR } ticket := authReq.GetSteam() if ticket == "" { - return nil, "", 0, "Steam ticket is required", 400 + return nil, "", 0, "Steam ticket is required", BAD_INPUT } else if invalidCharsRegex.MatchString(ticket) { - return nil, "", 0, "Invalid Steam ticket, no spaces or control characters allowed", 400 + return nil, "", 0, "Invalid Steam ticket, no spaces or control characters allowed", BAD_INPUT } steamProfile, err := a.socialClient.GetSteamProfile(a.config.GetSocial().Steam.PublisherKey, a.config.GetSocial().Steam.AppID, ticket) if err != nil { a.logger.Warn("Could not check Steam profile", zap.Error(err)) - return nil, "", 0, errorCouldNotLogin, 401 + return nil, "", 0, errorCouldNotLogin, AUTH_ERROR } var userID []byte @@ -525,25 +558,29 @@ func (a *authenticationService) loginSteam(authReq *AuthenticateRequest) ([]byte strconv.FormatUint(steamProfile.SteamID, 10)). Scan(&userID, &handle, &disabledAt) if err != nil { - a.logger.Warn("Could not login with Steam profile", zap.Error(err)) - return nil, "", 0, errorIDNotFound, 401 + if err == sql.ErrNoRows { + return nil, "", 0, errorIDNotFound, USER_NOT_FOUND + } else { + a.logger.Warn(errorCouldNotLogin, zap.String("profile", "steam"), zap.Error(err)) + return nil, "", 0, errorCouldNotLogin, RUNTIME_EXCEPTION + } } - return userID, handle, disabledAt, "", 200 + return userID, handle, disabledAt, "", 0 } -func (a *authenticationService) loginEmail(authReq *AuthenticateRequest) ([]byte, string, int64, string, int) { +func (a *authenticationService) loginEmail(authReq *AuthenticateRequest) ([]byte, string, int64, string, Error_Code) { email := authReq.GetEmail() if email == nil { - return nil, "", 0, errorInvalidPayload, 400 + return nil, "", 0, errorInvalidPayload, BAD_INPUT } else if email.Email == "" { - return nil, "", 0, "Email address is required", 400 + return nil, "", 0, "Email address is required", BAD_INPUT } else if invalidCharsRegex.MatchString(email.Email) { - return nil, "", 0, "Invalid email address, no spaces or control characters allowed", 400 + return nil, "", 0, "Invalid email address, no spaces or control characters allowed", BAD_INPUT } else if !emailRegex.MatchString(email.Email) { - return nil, "", 0, "Invalid email address format", 400 + return nil, "", 0, "Invalid email address format", BAD_INPUT } else if len(email.Email) < 10 || len(email.Email) > 255 { - return nil, "", 0, "Invalid email address, must be 10-255 bytes", 400 + return nil, "", 0, "Invalid email address, must be 10-255 bytes", BAD_INPUT } var userID []byte @@ -554,27 +591,30 @@ func (a *authenticationService) loginEmail(authReq *AuthenticateRequest) ([]byte strings.ToLower(email.Email)). Scan(&userID, &handle, &hashedPassword, &disabledAt) if err != nil { - a.logger.Warn(errorCouldNotLogin, zap.Error(err)) - return nil, "", 0, "Invalid credentials", 401 + if err == sql.ErrNoRows { + return nil, "", 0, errorIDNotFound, USER_NOT_FOUND + } else { + a.logger.Warn(errorCouldNotLogin, zap.String("profile", "email"), zap.Error(err)) + return nil, "", 0, errorCouldNotLogin, RUNTIME_EXCEPTION + } } err = bcrypt.CompareHashAndPassword(hashedPassword, []byte(email.Password)) if err != nil { - a.logger.Warn("Invalid credentials", zap.Error(err)) - return nil, "", 0, "Invalid credentials", 401 + return nil, "", 0, "Invalid credentials", AUTH_ERROR } - return userID, handle, disabledAt, "", 200 + return userID, handle, disabledAt, "", 0 } -func (a *authenticationService) loginCustom(authReq *AuthenticateRequest) ([]byte, string, int64, string, int) { +func (a *authenticationService) loginCustom(authReq *AuthenticateRequest) ([]byte, string, int64, string, Error_Code) { customID := authReq.GetCustom() if customID == "" { - return nil, "", 0, "Custom ID is required", 400 + return nil, "", 0, "Custom ID is required", BAD_INPUT } else if invalidCharsRegex.MatchString(customID) { - return nil, "", 0, "Invalid custom ID, no spaces or control characters allowed", 400 + return nil, "", 0, "Invalid custom ID, no spaces or control characters allowed", BAD_INPUT } else if len(customID) < 10 || len(customID) > 64 { - return nil, "", 0, "Invalid custom ID, must be 10-64 bytes", 400 + return nil, "", 0, "Invalid custom ID, must be 10-64 bytes", BAD_INPUT } var userID []byte @@ -584,16 +624,20 @@ func (a *authenticationService) loginCustom(authReq *AuthenticateRequest) ([]byt customID). Scan(&userID, &handle, &disabledAt) if err != nil { - a.logger.Warn(errorCouldNotLogin, zap.Error(err)) - return nil, "", 0, errorIDNotFound, 401 + if err == sql.ErrNoRows { + return nil, "", 0, errorIDNotFound, USER_NOT_FOUND + } else { + a.logger.Warn(errorCouldNotLogin, zap.String("profile", "custom"), zap.Error(err)) + return nil, "", 0, errorCouldNotLogin, RUNTIME_EXCEPTION + } } - return userID, handle, disabledAt, "", 200 + return userID, handle, disabledAt, "", 0 } -func (a *authenticationService) register(authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) register(authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { // Route to correct register handler - var registerFunc func(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, int) + var registerFunc func(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) var registerHook func(authReq *AuthenticateRequest, userID []byte, handle string) switch authReq.Id.(type) { @@ -616,18 +660,18 @@ func (a *authenticationService) register(authReq *AuthenticateRequest) ([]byte, case *AuthenticateRequest_Custom: registerFunc = a.registerCustom default: - return nil, "", errorInvalidPayload, 400 + return nil, "", errorInvalidPayload, BAD_INPUT } tx, err := a.db.Begin() if err != nil { a.logger.Warn("Could not register, transaction begin error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 500 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } userID, handle, errorMessage, errorCode := registerFunc(tx, authReq) - if errorCode != 200 { + if errorMessage != "" { if tx != nil { err = tx.Rollback() if err != nil { @@ -640,7 +684,7 @@ func (a *authenticationService) register(authReq *AuthenticateRequest) ([]byte, err = tx.Commit() if err != nil { a.logger.Error("Could not commit transaction", zap.Error(err)) - return nil, "", errorCouldNotRegister, 500 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } // Run any post-registration steps outside the main registration transaction. @@ -658,14 +702,14 @@ func (a *authenticationService) addUserEdgeMetadata(tx *sql.Tx, userID []byte, u return err } -func (a *authenticationService) registerDevice(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) registerDevice(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { deviceID := authReq.GetDevice() if deviceID == "" { - return nil, "", "Device ID is required", 400 + return nil, "", "Device ID is required", BAD_INPUT } else if invalidCharsRegex.MatchString(deviceID) { - return nil, "", "Invalid device ID, no spaces or control characters allowed", 400 + return nil, "", "Invalid device ID, no spaces or control characters allowed", BAD_INPUT } else if len(deviceID) < 10 || len(deviceID) > 64 { - return nil, "", "Invalid device ID, must be 10-64 bytes", 400 + return nil, "", "Invalid device ID, must be 10-64 bytes", BAD_INPUT } updatedAt := nowMs() @@ -682,44 +726,44 @@ WHERE NOT EXISTS FROM user_device WHERE id = $3)`, userID, handle, deviceID, updatedAt) + if err != nil { a.logger.Warn("Could not register new device profile, query error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - a.logger.Warn("Could not register new device profile, rows affected error") - return nil, "", errorIDAlreadyInUse, 401 + return nil, "", errorIDAlreadyInUse, USER_REGISTER_INUSE } res, err = tx.Exec("INSERT INTO user_device (id, user_id) VALUES ($1, $2)", deviceID, userID) if err != nil { a.logger.Warn("Could not register, query error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } if count, _ := res.RowsAffected(); count == 0 { - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } err = a.addUserEdgeMetadata(tx, userID, updatedAt) if err != nil { - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } - return userID, handle, "", 200 + return userID, handle, "", 0 } -func (a *authenticationService) registerFacebook(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) registerFacebook(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { accessToken := authReq.GetFacebook() if accessToken == "" { - return nil, "", errorAccessTokenIsRequired, 400 + return nil, "", errorAccessTokenIsRequired, BAD_INPUT } else if invalidCharsRegex.MatchString(accessToken) { - return nil, "", "Invalid Facebook access token, no spaces or control characters allowed", 400 + return nil, "", "Invalid Facebook access token, no spaces or control characters allowed", BAD_INPUT } fbProfile, err := a.socialClient.GetFacebookProfile(accessToken) if err != nil { a.logger.Warn("Could not get Facebook profile", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, AUTH_ERROR } updatedAt := nowMs() @@ -740,33 +784,32 @@ WHERE NOT EXISTS if err != nil { a.logger.Warn("Could not register new Facebook profile, query error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - a.logger.Warn("Could not register new Facebook profile, rows affected error") - return nil, "", errorIDAlreadyInUse, 401 + return nil, "", errorIDAlreadyInUse, USER_REGISTER_INUSE } err = a.addUserEdgeMetadata(tx, userID, updatedAt) if err != nil { - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } - return userID, handle, "", 200 + return userID, handle, "", 0 } -func (a *authenticationService) registerGoogle(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) registerGoogle(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { accessToken := authReq.GetGoogle() if accessToken == "" { - return nil, "", errorAccessTokenIsRequired, 400 + return nil, "", errorAccessTokenIsRequired, BAD_INPUT } else if invalidCharsRegex.MatchString(accessToken) { - return nil, "", "Invalid Google access token, no spaces or control characters allowed", 400 + return nil, "", "Invalid Google access token, no spaces or control characters allowed", BAD_INPUT } googleProfile, err := a.socialClient.GetGoogleProfile(accessToken) if err != nil { a.logger.Warn("Could not get Google profile", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, AUTH_ERROR } updatedAt := nowMs() @@ -790,31 +833,30 @@ WHERE NOT EXISTS if err != nil { a.logger.Warn("Could not register new Google profile, query error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - a.logger.Warn("Could not register new Google profile, rows affected error") - return nil, "", errorIDAlreadyInUse, 401 + return nil, "", errorIDAlreadyInUse, USER_REGISTER_INUSE } err = a.addUserEdgeMetadata(tx, userID, updatedAt) if err != nil { - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } - return userID, handle, "", 200 + return userID, handle, "", 0 } -func (a *authenticationService) registerGameCenter(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) registerGameCenter(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { gc := authReq.GetGameCenter() if gc == nil || gc.PlayerId == "" || gc.BundleId == "" || gc.Timestamp == 0 || gc.Salt == "" || gc.Signature == "" || gc.PublicKeyUrl == "" { - return nil, "", errorInvalidPayload, 400 + return nil, "", errorInvalidPayload, BAD_INPUT } _, err := a.socialClient.CheckGameCenterID(gc.PlayerId, gc.BundleId, gc.Timestamp, gc.Salt, gc.Signature, gc.PublicKeyUrl) if err != nil { a.logger.Warn("Could not get Game Center profile", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, AUTH_ERROR } updatedAt := nowMs() @@ -838,37 +880,36 @@ WHERE NOT EXISTS if err != nil { a.logger.Warn("Could not register new Game Center profile, query error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - a.logger.Warn("Could not register new Game Center profile, rows affected error") - return nil, "", errorIDAlreadyInUse, 401 + return nil, "", errorIDAlreadyInUse, USER_REGISTER_INUSE } err = a.addUserEdgeMetadata(tx, userID, updatedAt) if err != nil { - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } - return userID, handle, "", 200 + return userID, handle, "", 0 } -func (a *authenticationService) registerSteam(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) registerSteam(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { if a.config.GetSocial().Steam.PublisherKey == "" || a.config.GetSocial().Steam.AppID == 0 { - return nil, "", "Steam registration not available", 401 + return nil, "", "Steam registration not available", AUTH_ERROR } ticket := authReq.GetSteam() if ticket == "" { - return nil, "", "Steam ticket is required", 400 + return nil, "", "Steam ticket is required", BAD_INPUT } else if invalidCharsRegex.MatchString(ticket) { - return nil, "", "Invalid Steam ticket, no spaces or control characters allowed", 400 + return nil, "", "Invalid Steam ticket, no spaces or control characters allowed", BAD_INPUT } steamProfile, err := a.socialClient.GetSteamProfile(a.config.GetSocial().Steam.PublisherKey, a.config.GetSocial().Steam.AppID, ticket) if err != nil { a.logger.Warn("Could not get Steam profile", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, AUTH_ERROR } updatedAt := nowMs() @@ -892,35 +933,34 @@ WHERE NOT EXISTS if err != nil { a.logger.Warn("Could not register new Steam profile, query error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - a.logger.Warn("Could not register new Steam profile, rows affected error") - return nil, "", errorIDAlreadyInUse, 401 + return nil, "", errorIDAlreadyInUse, USER_REGISTER_INUSE } err = a.addUserEdgeMetadata(tx, userID, updatedAt) if err != nil { - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } - return userID, handle, "", 200 + return userID, handle, "", 0 } -func (a *authenticationService) registerEmail(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) registerEmail(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { email := authReq.GetEmail() if email == nil { - return nil, "", errorInvalidPayload, 400 + return nil, "", errorInvalidPayload, BAD_INPUT } else if email.Email == "" { - return nil, "", "Email address is required", 400 + return nil, "", "Email address is required", BAD_INPUT } else if invalidCharsRegex.MatchString(email.Email) { - return nil, "", "Invalid email address, no spaces or control characters allowed", 400 + return nil, "", "Invalid email address, no spaces or control characters allowed", BAD_INPUT } else if len(email.Password) < 8 { - return nil, "", "Password must be longer than 8 characters", 400 + return nil, "", "Password must be longer than 8 characters", BAD_INPUT } else if !emailRegex.MatchString(email.Email) { - return nil, "", "Invalid email address format", 400 + return nil, "", "Invalid email address format", BAD_INPUT } else if len(email.Email) < 10 || len(email.Email) > 255 { - return nil, "", "Invalid email address, must be 10-255 bytes", 400 + return nil, "", "Invalid email address, must be 10-255 bytes", BAD_INPUT } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(email.Password), bcrypt.DefaultCost) @@ -948,29 +988,28 @@ WHERE NOT EXISTS if err != nil { a.logger.Warn("Could not register new email profile, query error", zap.Error(err)) - return nil, "", "Email already in use", 401 + return nil, "", "Email already in use", RUNTIME_EXCEPTION } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - a.logger.Warn("Could not register new email profile, rows affected error") - return nil, "", errorIDAlreadyInUse, 401 + return nil, "", errorIDAlreadyInUse, USER_REGISTER_INUSE } err = a.addUserEdgeMetadata(tx, userID, updatedAt) if err != nil { - return nil, "", "Email already in use", 401 + return nil, "", "Email already in use", RUNTIME_EXCEPTION } - return userID, handle, "", 200 + return userID, handle, "", 0 } -func (a *authenticationService) registerCustom(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, int) { +func (a *authenticationService) registerCustom(tx *sql.Tx, authReq *AuthenticateRequest) ([]byte, string, string, Error_Code) { customID := authReq.GetCustom() if customID == "" { - return nil, "", "Custom ID is required", 400 + return nil, "", "Custom ID is required", BAD_INPUT } else if invalidCharsRegex.MatchString(customID) { - return nil, "", "Invalid custom ID, no spaces or control characters allowed", 400 + return nil, "", "Invalid custom ID, no spaces or control characters allowed", BAD_INPUT } else if len(customID) < 10 || len(customID) > 64 { - return nil, "", "Invalid custom ID, must be 10-64 bytes", 400 + return nil, "", "Invalid custom ID, must be 10-64 bytes", BAD_INPUT } updatedAt := nowMs() @@ -994,20 +1033,19 @@ WHERE NOT EXISTS if err != nil { a.logger.Warn("Could not register new custom profile, query error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { - a.logger.Warn("Could not register new custom profile, rows affected error") - return nil, "", errorIDAlreadyInUse, 401 + return nil, "", errorIDAlreadyInUse, USER_REGISTER_INUSE } err = a.addUserEdgeMetadata(tx, userID, updatedAt) if err != nil { a.logger.Error("Could not register new custom profile, user edge metadata error", zap.Error(err)) - return nil, "", errorCouldNotRegister, 401 + return nil, "", errorCouldNotRegister, RUNTIME_EXCEPTION } - return userID, handle, "", 200 + return userID, handle, "", 0 } func (a *authenticationService) generateHandle() string {