2016-04-05 20:22:13 +00:00
package v1
import (
"crypto/md5"
"database/sql"
"fmt"
2016-04-19 14:07:27 +00:00
"git.zxq.co/ripple/rippleapi/common"
2016-04-05 20:22:13 +00:00
"golang.org/x/crypto/bcrypt"
)
type tokenNewInData struct {
// either username or userid must be given in the request.
// if none is given, the request is trashed.
Username string ` json:"username" `
UserID int ` json:"id" `
Password string ` json:"password" `
Privileges int ` json:"privileges" `
Description string ` json:"description" `
}
2016-04-16 16:05:24 +00:00
type tokenNewResponse struct {
common . ResponseBase
2016-04-05 20:22:13 +00:00
Username string ` json:"username" `
ID int ` json:"id" `
Privileges int ` json:"privileges" `
Token string ` json:"token,omitempty" `
Banned bool ` json:"banned" `
}
// TokenNewPOST is the handler for POST /token/new.
2016-04-16 16:05:24 +00:00
func TokenNewPOST ( md common . MethodData ) common . CodeMessager {
var r tokenNewResponse
2016-04-05 20:22:13 +00:00
data := tokenNewInData { }
2016-04-12 19:23:02 +00:00
err := md . RequestData . Unmarshal ( & data )
2016-04-05 20:22:13 +00:00
if err != nil {
2016-04-16 16:05:24 +00:00
return ErrBadJSON
2016-04-05 20:22:13 +00:00
}
var miss [ ] string
if data . Username == "" && data . UserID == 0 {
miss = append ( miss , "username|id" )
}
if data . Password == "" {
miss = append ( miss , "password" )
}
if len ( miss ) != 0 {
2016-04-16 16:05:24 +00:00
return ErrMissingField ( miss ... )
2016-04-05 20:22:13 +00:00
}
var q * sql . Row
const base = "SELECT id, username, rank, password_md5, password_version, allowed FROM users "
if data . UserID != 0 {
q = md . DB . QueryRow ( base + "WHERE id = ? LIMIT 1" , data . UserID )
} else {
q = md . DB . QueryRow ( base + "WHERE username = ? LIMIT 1" , data . Username )
}
var (
rank int
pw string
pwVersion int
allowed int
)
2016-04-16 16:05:24 +00:00
err = q . Scan ( & r . ID , & r . Username , & rank , & pw , & pwVersion , & allowed )
2016-04-05 20:22:13 +00:00
switch {
case err == sql . ErrNoRows :
2016-04-16 16:05:24 +00:00
return common . SimpleResponse ( 404 , "No user with that username/id was found." )
2016-04-05 20:22:13 +00:00
case err != nil :
2016-04-08 17:05:54 +00:00
md . Err ( err )
2016-04-16 16:05:24 +00:00
return Err500
2016-04-05 20:22:13 +00:00
}
2016-04-16 16:05:24 +00:00
if nFailedAttempts ( r . ID ) > 20 {
return common . SimpleResponse ( 429 , "You've made too many login attempts. Try again later." )
2016-04-10 09:51:34 +00:00
}
2016-04-05 20:22:13 +00:00
if pwVersion == 1 {
2016-04-16 16:05:24 +00:00
return common . SimpleResponse ( 418 , "That user still has a password in version 1. Unfortunately, in order for the API to check for the password to be OK, the user has to first log in through the website." )
2016-04-05 20:22:13 +00:00
}
if err := bcrypt . CompareHashAndPassword ( [ ] byte ( pw ) , [ ] byte ( fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( data . Password ) ) ) ) ) ; err != nil {
if err == bcrypt . ErrMismatchedHashAndPassword {
2016-04-16 16:05:24 +00:00
go addFailedAttempt ( r . ID )
return common . SimpleResponse ( 403 , "That password doesn't match!" )
2016-04-05 20:22:13 +00:00
}
2016-04-08 17:05:54 +00:00
md . Err ( err )
2016-04-16 16:05:24 +00:00
return Err500
2016-04-05 20:22:13 +00:00
}
if allowed == 0 {
r . Code = 200
r . Message = "That user is banned."
2016-04-16 16:05:24 +00:00
r . Banned = true
return r
2016-04-05 20:22:13 +00:00
}
2016-04-16 16:05:24 +00:00
r . Privileges = int ( common . Privileges ( data . Privileges ) . CanOnly ( rank ) )
2016-04-05 20:22:13 +00:00
var (
tokenStr string
tokenMD5 string
)
for {
tokenStr = common . RandomString ( 32 )
tokenMD5 = fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( tokenStr ) ) )
2016-04-16 16:05:24 +00:00
r . Token = tokenStr
2016-04-05 20:22:13 +00:00
id := 0
2016-04-07 10:43:39 +00:00
err := md . DB . QueryRow ( "SELECT id FROM tokens WHERE token=? LIMIT 1" , tokenMD5 ) . Scan ( & id )
2016-04-05 20:22:13 +00:00
if err == sql . ErrNoRows {
break
}
if err != nil {
2016-04-08 17:05:54 +00:00
md . Err ( err )
2016-04-16 16:05:24 +00:00
return Err500
2016-04-05 20:22:13 +00:00
}
}
2016-05-14 18:18:29 +00:00
_ , err = md . DB . Exec ( "INSERT INTO tokens(user, privileges, description, token, private) VALUES (?, ?, ?, ?, '0')" , r . ID , r . Privileges , data . Description , tokenMD5 )
2016-04-05 20:22:13 +00:00
if err != nil {
2016-04-08 17:05:54 +00:00
md . Err ( err )
2016-04-16 16:05:24 +00:00
return Err500
2016-04-05 20:22:13 +00:00
}
r . Code = 200
2016-04-16 16:05:24 +00:00
return r
2016-04-05 20:22:13 +00:00
}
2016-06-14 08:03:53 +00:00
// TokenSelfDeleteGET deletes the token the user is connecting with.
func TokenSelfDeleteGET ( md common . MethodData ) common . CodeMessager {
if md . ID ( ) == 0 {
return common . SimpleResponse ( 400 , "How should we delete your token if you haven't even given us one?!" )
}
_ , err := md . DB . Exec ( "DELETE FROM tokens WHERE token = ? LIMIT 1" ,
fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( md . User . Value ) ) ) )
if err != nil {
md . Err ( err )
return Err500
}
return common . SimpleResponse ( 200 , "Bye!" )
}