// Fetch the claims of a user. func fetchClaims(d *sql.DB, user User) (User, *utils.GoAuthError) { rows, err := d.Query("SELECT uc.value, c.name, c.required FROM user_claim AS uc INNER JOIN claim AS c ON c.name=uc.claim WHERE uc.user=?", user.id) if err != nil { log.Println(err) customErr := utils.NewGoAuthError(500, "Could not execute the query") return User{}, &customErr } defer rows.Close() user.claims = make(map[Claim]UserClaim) claim := Claim{} var value UserClaim for rows.Next() { err := rows.Scan(&value, &claim.name, &claim.required) if err != nil { log.Println(err) customErr := utils.NewGoAuthError(500, "Could not parse the claim") return User{}, &customErr } user.claims[claim] = value } err = rows.Err() if err != nil { log.Println(err) customErr := utils.NewGoAuthError(500, "Could not could not parse all claims") return User{}, &customErr } return user, nil }
// Store a new user in the repository. Validates if the given email looks like // an email and is not yet taken by somebody. If these conditions have been // met then the new user will be stored and a verification email will be sent // to the new user. func (s UserService) Register(email, password string) (User, *utils.GoAuthError) { emptyUser := User{} if !utils.ValidateEmail(email) { customErr := utils.NewGoAuthError(400, "Invalid email adres") return emptyUser, &customErr } user, err := s.repository.findByEmail(email) // Email was not yet taken. if err.Status == 404 { user = NewUser(email, password) // Validate if the ID is already taken or not. taken, err := s.repository.isTakenID(user.id) for taken { user.generateNewID() taken, err = s.repository.isTakenID(user.id) if err != nil { return emptyUser, err } } // Validate if the verification token is already taken taken, err = s.repository.isTakenToken(user.email.token) for taken { user.email.GenerateNewToken() taken, err = s.repository.isTakenToken(user.email.token) if err != nil { return emptyUser, err } } err = s.repository.saveUser(user) if err != nil { return emptyUser, err } // @TODO: send an verification email to the new user. return user, nil } // Some other error occured. if err != nil { return emptyUser, err } // User was found without any errors. Email is already taken. customErr := utils.NewGoAuthError(409, "Email is already taken") return emptyUser, &customErr }
func validate(token string) (map[string]interface{}, *utils.GoAuthError) { split := strings.Split(token, " ") if len(split) != 2 { customErr := utils.NewGoAuthError(401, "Unauthorized") return nil, &customErr } decoded, err := gose.DecodePayloadWhenValid(split[1], core.Configuration.JWTSecret) if err != nil { customErr := utils.NewGoAuthError(401, "Unauthorized") return nil, &customErr } return decoded, nil }
// Allow to change the password, if the `old` arg matches the current // password. func (u *User) SetPassword(old, new string) *utils.GoAuthError { if u.password.Validate(old) { u.setPassword(new) return nil } goAutErr := utils.NewGoAuthError(400, "Wrong password") return &goAutErr }
// Store the given user in the database. This is done by several "upsert" // commands. All of them have to succeed. func (r UserRepository) saveUser(user User) *utils.GoAuthError { tx, err := r.database.Begin() if err != nil { log.Println(err) customErr := utils.NewGoAuthError(500, "Could not start a transaction") return &customErr } _, userInsertErr := r.database.Exec("INSERT OR IGNORE INTO user (id) VALUES (?)", user.id) _, passwordInsertErr := r.database.Exec("INSERT OR IGNORE INTO password (user, hash, salt) VALUES (?, ?, ?)", user.id, user.password.hash, user.password.salt) _, passwordUpdateErr := r.database.Exec("UPDATE password SET hash=?, salt=? WHERE user=?", user.password.hash, user.password.salt, user.id) _, emailInsertErr := r.database.Exec("INSERT OR IGNORE INTO email (user, email, token, verified) VALUES (?, ?, ?, ?)", user.id, user.email.email, user.email.token, user.email.verified) _, emailUpdateErr := r.database.Exec("UPDATE email SET email=?, token=?, verified=? WHERE user=?", user.email.email, user.email.token, user.email.verified, user.id) if !utils.AreNil(userInsertErr, passwordInsertErr, passwordUpdateErr, emailInsertErr, emailUpdateErr) { log.Println(userInsertErr, passwordInsertErr, passwordUpdateErr, emailInsertErr, emailUpdateErr) tx.Rollback() customErr := utils.NewGoAuthError(500, "Could not upsert the given user") return &customErr } for k, v := range user.claims { _, userClaimInsertErr := r.database.Exec("INSERT OR IGNORE INTO user_claim (user, claim, value) VALUES (?, ?, ?)", user.id, k.name, v) _, userClaimUpdateErr := r.database.Exec("UPDATE user_claim SET value=? WHERE user=? AND claim=?", v, user.id, k.name) if !utils.AreNil(userClaimInsertErr, userClaimUpdateErr) { log.Println(userClaimInsertErr, userClaimUpdateErr) tx.Rollback() customErr := utils.NewGoAuthError(500, "Could not upsert the user's claims") return &customErr } } err = tx.Commit() if err != nil { log.Println(err) customErr := utils.NewGoAuthError(500, "Could not conclude the transaction") return &customErr } return nil }
// Handles the logic for the user login. Find a user and validate if the // password matches the stored password. func (s UserService) LogIn(email, password string) (User, *utils.GoAuthError) { user, err := s.repository.findByEmail(email) if err != nil { return User{}, err } if user.password.Validate(password) { return user, nil } customErr := utils.NewGoAuthError(400, "Wrong email/password combo") return User{}, &customErr }
// Check if the given token already is taken by another user. func (r UserRepository) isTakenToken(token string) (bool, *utils.GoAuthError) { row := r.database.QueryRow("SELECT email FROM email WHERE token=?", token) email := Email{} err := row.Scan(&email.email) if err != nil && err.Error() != "sql: no rows in result set" { log.Println(err) customErr := utils.NewGoAuthError(500, "Could not execute the query") return false, &customErr } // No results would mean err is not nil, ergo when the token is not yet // taken err is not nil. return err == nil, nil }
// Check if there already is a user bound to the given ID. func (r UserRepository) isTakenID(id string) (bool, *utils.GoAuthError) { row := r.database.QueryRow("SELECT * FROM user WHERE id=?", id) user := User{} err := row.Scan(&user.id) if err != nil && err.Error() != "sql: no rows in result set" { log.Println(err) customErr := utils.NewGoAuthError(500, "Could not execute the query") return false, &customErr } // No results would mean err is not nil, ergo when the ID is not yet taken // err is not nil. return err == nil, nil }
// A helper function to parse a row as a user. func parseUserRow(row *sql.Row) (User, *utils.GoAuthError) { user := User{} email := Email{} password := Password{} err := row.Scan(&user.id, &email.email, &email.verified, &email.token, &password.hash, &password.salt) if err != nil { log.Println(err) customErr := utils.NewGoAuthError(404, "Could not find that user") return User{}, &customErr } user.email = email user.password = password return user, nil }