142 lines
3.2 KiB
Go
142 lines
3.2 KiB
Go
package v1
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/osuripple/api/common"
|
|
"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"`
|
|
}
|
|
|
|
type tokenNewOutData struct {
|
|
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.
|
|
func TokenNewPOST(md common.MethodData) (r common.Response) {
|
|
data := tokenNewInData{}
|
|
err := json.Unmarshal(md.RequestData, &data)
|
|
if err != nil {
|
|
r = ErrBadJSON
|
|
return
|
|
}
|
|
|
|
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 {
|
|
r = ErrMissingField(miss...)
|
|
return
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
ret := tokenNewOutData{}
|
|
var (
|
|
rank int
|
|
pw string
|
|
pwVersion int
|
|
allowed int
|
|
)
|
|
|
|
err = q.Scan(&ret.ID, &ret.Username, &rank, &pw, &pwVersion, &allowed)
|
|
switch {
|
|
case err == sql.ErrNoRows:
|
|
r.Code = 404
|
|
r.Message = "No user with that username/id was found."
|
|
return
|
|
case err != nil:
|
|
md.Err(err)
|
|
r = Err500
|
|
return
|
|
}
|
|
|
|
if nFailedAttempts(ret.ID) > 20 {
|
|
r.Code = 429
|
|
r.Message = "You've made too many login attempts. Try again later."
|
|
return
|
|
}
|
|
|
|
if pwVersion == 1 {
|
|
r.Code = 418 // Teapots!
|
|
r.Message = "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."
|
|
return
|
|
}
|
|
if err := bcrypt.CompareHashAndPassword([]byte(pw), []byte(fmt.Sprintf("%x", md5.Sum([]byte(data.Password))))); err != nil {
|
|
if err == bcrypt.ErrMismatchedHashAndPassword {
|
|
go addFailedAttempt(ret.ID)
|
|
r.Code = 403
|
|
r.Message = "That password doesn't match!"
|
|
return
|
|
}
|
|
md.Err(err)
|
|
r = Err500
|
|
return
|
|
}
|
|
if allowed == 0 {
|
|
r.Code = 200
|
|
r.Message = "That user is banned."
|
|
ret.Banned = true
|
|
r.Data = ret
|
|
return
|
|
}
|
|
ret.Privileges = int(common.Privileges(data.Privileges).CanOnly(rank))
|
|
|
|
var (
|
|
tokenStr string
|
|
tokenMD5 string
|
|
)
|
|
for {
|
|
tokenStr = common.RandomString(32)
|
|
tokenMD5 = fmt.Sprintf("%x", md5.Sum([]byte(tokenStr)))
|
|
ret.Token = tokenStr
|
|
id := 0
|
|
|
|
err := md.DB.QueryRow("SELECT id FROM tokens WHERE token=? LIMIT 1", tokenMD5).Scan(&id)
|
|
if err == sql.ErrNoRows {
|
|
break
|
|
}
|
|
if err != nil {
|
|
md.Err(err)
|
|
r = Err500
|
|
return
|
|
}
|
|
}
|
|
_, err = md.DB.Exec("INSERT INTO tokens(user, privileges, description, token) VALUES (?, ?, ?, ?)", ret.ID, ret.Privileges, data.Description, tokenMD5)
|
|
if err != nil {
|
|
md.Err(err)
|
|
r = Err500
|
|
return
|
|
}
|
|
|
|
r.Code = 200
|
|
r.Data = ret
|
|
return
|
|
}
|