2016-04-05 20:22:13 +00:00
package v1
import (
"crypto/md5"
2017-06-17 16:11:10 +00:00
"crypto/sha256"
2016-04-05 20:22:13 +00:00
"database/sql"
2017-06-17 16:11:10 +00:00
"encoding/json"
"errors"
2016-04-05 20:22:13 +00:00
"fmt"
2017-06-17 16:11:10 +00:00
"strconv"
2016-12-01 22:16:36 +00:00
"time"
2016-04-05 20:22:13 +00:00
2016-08-15 11:37:03 +00:00
"github.com/jmoiron/sqlx"
2017-02-02 12:40:28 +00:00
"golang.org/x/crypto/bcrypt"
2017-01-14 17:06:16 +00:00
"zxq.co/ripple/rippleapi/common"
"zxq.co/ripple/rippleapi/limit"
"zxq.co/ripple/schiavolib"
2016-04-05 20:22:13 +00:00
)
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 { }
2017-02-02 12:40:28 +00:00
err := md . 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
}
2016-10-02 17:47:31 +00:00
md . Doggo . Incr ( "tokens.new" , nil , 1 )
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
2016-07-03 22:06:23 +00:00
const base = "SELECT id, username, privileges, password_md5, password_version, privileges FROM users "
2016-04-05 20:22:13 +00:00
if data . UserID != 0 {
q = md . DB . QueryRow ( base + "WHERE id = ? LIMIT 1" , data . UserID )
} else {
2016-10-16 16:52:34 +00:00
q = md . DB . QueryRow ( base + "WHERE username = ? LIMIT 1" , common . SafeUsername ( data . Username ) )
2016-04-05 20:22:13 +00:00
}
var (
2016-08-27 10:04:12 +00:00
rank int
pw string
pwVersion int
privilegesRaw uint64
2016-04-05 20:22:13 +00:00
)
2016-08-27 10:04:12 +00:00
err = q . Scan ( & r . ID , & r . Username , & rank , & pw , & pwVersion , & privilegesRaw )
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-08-27 10:04:12 +00:00
privileges := common . UserPrivileges ( privilegesRaw )
2016-04-05 20:22:13 +00:00
2017-02-02 12:40:28 +00:00
if ! limit . NonBlockingRequest ( fmt . Sprintf ( "loginattempt:%d:%s" , r . ID , md . ClientIP ( ) ) , 5 ) {
2016-04-16 16:05:24 +00:00
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
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
}
2016-07-03 22:06:23 +00:00
const want = ( common . UserPrivilegePublic | common . UserPrivilegeNormal )
if ( privileges & want ) != want {
2016-07-10 18:53:06 +00:00
r . Code = 402
2016-04-05 20:22:13 +00:00
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-07-03 22:06:23 +00:00
r . Privileges = int ( common . Privileges ( data . Privileges ) . CanOnly ( privileges ) )
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-07-03 22:06:23 +00:00
var id int
2016-04-05 20:22:13 +00:00
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-12-01 22:16:36 +00:00
_ , err = md . DB . Exec ( "INSERT INTO tokens(user, privileges, description, token, private, last_updated) VALUES (?, ?, ?, ?, '0', ?)" ,
r . ID , r . Privileges , data . Description , tokenMD5 , time . Now ( ) . Unix ( ) )
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
2016-11-21 16:04:27 +00:00
// TokenSelfDeletePOST deletes the token the user is connecting with.
func TokenSelfDeletePOST ( md common . MethodData ) common . CodeMessager {
2016-06-14 08:03:53 +00:00
if md . ID ( ) == 0 {
return common . SimpleResponse ( 400 , "How should we delete your token if you haven't even given us one?!" )
}
2017-06-17 16:11:10 +00:00
var err error
if md . IsBearer ( ) {
_ , err = md . DB . Exec ( "DELETE FROM osin_access WHERE access_token = ? LIMIT 1" ,
fmt . Sprintf ( "%x" , sha256 . Sum256 ( [ ] byte ( md . User . Value ) ) ) )
} else {
_ , err = md . DB . Exec ( "DELETE FROM tokens WHERE token = ? LIMIT 1" ,
fmt . Sprintf ( "%x" , md5 . Sum ( [ ] byte ( md . User . Value ) ) ) )
}
2016-06-14 08:03:53 +00:00
if err != nil {
md . Err ( err )
return Err500
}
return common . SimpleResponse ( 200 , "Bye!" )
}
2016-06-14 09:32:11 +00:00
type token struct {
2016-12-01 22:16:36 +00:00
ID int ` json:"id" `
Privileges uint64 ` json:"privileges" `
Description string ` json:"description" `
LastUpdated common . UnixTimestamp ` json:"last_updated" `
2016-06-14 09:32:11 +00:00
}
type tokenResponse struct {
common . ResponseBase
2016-07-10 18:53:06 +00:00
Tokens [ ] token ` json:"tokens" `
2016-06-14 09:32:11 +00:00
}
// TokenGET retrieves a list listing all the user's public tokens.
func TokenGET ( md common . MethodData ) common . CodeMessager {
2017-06-17 16:11:10 +00:00
rows , err := md . DB . Query ( "SELECT id, privileges, description, last_updated FROM tokens WHERE user = ? AND private = '0' " + common . Paginate ( md . Query ( "p" ) , md . Query ( "l" ) , 50 ) , md . ID ( ) )
2016-06-14 09:32:11 +00:00
if err != nil {
return Err500
}
var r tokenResponse
for rows . Next ( ) {
var t token
2016-12-01 22:16:36 +00:00
err = rows . Scan ( & t . ID , & t . Privileges , & t . Description , & t . LastUpdated )
2016-06-14 09:32:11 +00:00
if err != nil {
md . Err ( err )
continue
}
r . Tokens = append ( r . Tokens , t )
}
r . Code = 200
return r
}
2017-06-17 16:11:10 +00:00
type oauthClient struct {
ID string ` json:"id" `
Name string ` json:"name" `
OwnerID int ` json:"owner_id" `
Avatar string ` json:"avatar" `
}
// Scan scans the extra in the mysql table into Name, OwnerID and Avatar.
func ( o * oauthClient ) Scan ( src interface { } ) error {
var s [ ] byte
switch x := src . ( type ) {
case string :
s = [ ] byte ( x )
case [ ] byte :
s = x
default :
return errors . New ( "Can't scan non-string" )
}
var vals [ 3 ] string
err := json . Unmarshal ( s , & vals )
if err != nil {
return err
}
o . Name = vals [ 0 ]
o . OwnerID , _ = strconv . Atoi ( vals [ 1 ] )
o . Avatar = vals [ 2 ]
return nil
}
type bearerToken struct {
Client oauthClient ` json:"client" `
Scope string ` json:"scope" `
Privileges common . Privileges ` json:"privileges" `
Created time . Time ` json:"created" `
}
2016-06-14 10:01:30 +00:00
type tokenSingleResponse struct {
common . ResponseBase
token
}
2017-06-17 16:11:10 +00:00
type bearerTokenSingleResponse struct {
common . ResponseBase
bearerToken
}
2016-06-14 10:01:30 +00:00
// TokenSelfGET retrieves information about the token the user is connecting with.
func TokenSelfGET ( md common . MethodData ) common . CodeMessager {
2016-08-09 17:22:41 +00:00
if md . ID ( ) == 0 {
return common . SimpleResponse ( 404 , "How are we supposed to find the token you're using if you ain't even using one?!" )
}
2017-06-17 16:11:10 +00:00
if md . IsBearer ( ) {
return getBearerToken ( md )
}
2016-06-14 10:01:30 +00:00
var r tokenSingleResponse
// md.User.ID = token id, userid would have been md.User.UserID. what a clusterfuck
2017-06-17 16:11:10 +00:00
err := md . DB . QueryRow ( "SELECT id, privileges, description, last_updated FROM tokens WHERE id = ?" , md . User . ID ) . Scan (
2016-12-01 22:16:36 +00:00
& r . ID , & r . Privileges , & r . Description , & r . LastUpdated ,
2016-06-14 10:01:30 +00:00
)
if err != nil {
md . Err ( err )
return Err500
}
r . Code = 200
return r
}
2017-06-17 16:11:10 +00:00
func getBearerToken ( md common . MethodData ) common . CodeMessager {
var b bearerTokenSingleResponse
err := md . DB .
QueryRow ( `
SELECT t . scope , t . created_at , c . id , c . extra
FROM osin_access t INNER JOIN osin_client c
WHERE t . access_token = ?
` , fmt . Sprintf ( "%x" , sha256 . Sum256 ( [ ] byte ( md . User . Value ) ) ) ) . Scan (
& b . Scope , & b . Created , & b . Client . ID , & b . Client ,
)
if err != nil {
md . Err ( err )
return Err500
}
b . Code = 200
b . Privileges = md . User . TokenPrivileges
return b
}
2016-11-21 16:04:27 +00:00
// TokenFixPrivilegesPOST fixes the privileges on the token of the given user,
2016-06-14 09:32:11 +00:00
// or of all the users if no user is given.
2016-11-21 16:04:27 +00:00
func TokenFixPrivilegesPOST ( md common . MethodData ) common . CodeMessager {
2016-08-15 17:59:46 +00:00
id := common . Int ( md . Query ( "id" ) )
if md . Query ( "id" ) == "self" {
2016-06-14 09:32:11 +00:00
id = md . ID ( )
}
go fixPrivileges ( id , md . DB )
return common . SimpleResponse ( 200 , "Privilege fixing started!" )
}
2016-08-15 11:37:03 +00:00
func fixPrivileges ( user int , db * sqlx . DB ) {
2016-06-14 09:32:11 +00:00
var wc string
var params = make ( [ ] interface { } , 0 , 1 )
if user != 0 {
// dirty, but who gives a shit
wc = "WHERE user = ?"
params = append ( params , user )
}
rows , err := db . Query ( `
SELECT
2017-06-17 16:11:10 +00:00
tokens . id , tokens . privileges , users . privileges
2016-06-14 09:32:11 +00:00
FROM tokens
LEFT JOIN users ON users . id = tokens . user
` + wc , params ... )
if err != nil {
fmt . Println ( err )
schiavo . Bunker . Send ( err . Error ( ) )
return
}
for rows . Next ( ) {
var (
2016-08-27 10:04:12 +00:00
id int
privsRaw uint64
privs common . Privileges
newPrivs common . Privileges
privilegesRaw uint64
2016-06-14 09:32:11 +00:00
)
2016-08-27 10:04:12 +00:00
err := rows . Scan ( & id , & privsRaw , & privilegesRaw )
if err != nil {
fmt . Println ( err )
continue
}
privileges := common . UserPrivileges ( privilegesRaw )
2016-06-14 09:32:11 +00:00
privs = common . Privileges ( privsRaw )
2016-07-03 22:06:23 +00:00
newPrivs = privs . CanOnly ( privileges )
2016-06-14 09:32:11 +00:00
if newPrivs != privs {
_ , err := db . Exec ( "UPDATE tokens SET privileges = ? WHERE id = ? LIMIT 1" , uint64 ( newPrivs ) , id )
if err != nil {
fmt . Println ( err )
schiavo . Bunker . Send ( err . Error ( ) )
continue
}
}
}
}