tokens tied to useragent

This commit is contained in:
2022-12-27 22:21:54 -06:00
parent 14047df959
commit 71fd5f0d32
6 changed files with 175 additions and 49 deletions

View File

@@ -15,26 +15,37 @@ const (
) )
func getAuthorization(c *gin.Context) (AuthorizationScope, string) { func getAuthorization(c *gin.Context) (AuthorizationScope, string) {
//get auth header
header := c.GetHeader("Authorization") header := c.GetHeader("Authorization")
if header == "" { if header == "" {
return AuthorizationScopeNone, "" return AuthorizationScopeNone, ""
} }
//check if user is authorized
headerSpl := strings.Split(header, " ") headerSpl := strings.Split(header, " ")
if len(headerSpl) != 2 { if len(headerSpl) != 2 {
return AuthorizationScopeNone, "" return AuthorizationScopeNone, ""
} }
prefix := headerSpl[0] prefix := headerSpl[0]
token := strings.ToLower(headerSpl[1]) token := strings.ToLower(headerSpl[1])
if prefix == "Bearer" {
if storage.CheckLoginToken(token, c.ClientIP()) {
return AuthorizationScopeUser, token
}
}
if prefix == "Bot" { if prefix == "Bot" {
//attempt to authorize as bot
if found, _ := storage.BotTokenFromToken(token); found { if found, _ := storage.BotTokenFromToken(token); found {
return AuthorizationScopeBot, token return AuthorizationScopeBot, token
} }
} }
if prefix == "Bearer" {
//attempt to authorize as user
userAgentString := c.GetHeader("User-Agent")
if userAgentString == "" {
return AuthorizationScopeNone, ""
}
ua := storage.ParseUA(userAgentString)
if storage.CheckLoginToken(token, c.ClientIP(), ua) {
return AuthorizationScopeUser, token
}
}
return AuthorizationScopeNone, "" return AuthorizationScopeNone, ""
} }

View File

@@ -24,7 +24,8 @@ func Run() {
public := r.Group("/") public := r.Group("/")
public.POST("/login", login) //web login public.POST("/login/password", loginPassword) //web login
public.POST("/login/token", loginToken) //web login
public.GET("/access", access) //access token public.GET("/access", access) //access token
private := r.Group("/") private := r.Group("/")

View File

@@ -7,19 +7,30 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type LoginBody struct {
type LoginPasswordBody struct {
Username string `json:"username" binding:"required"` Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"` Password string `json:"password" binding:"required"`
} }
func login(c *gin.Context) { type LoginTokenBody struct {
var loginBody LoginBody Token string `json:"token" binding:"required"`
}
func loginPassword(c *gin.Context) {
var loginBody LoginPasswordBody
if err := c.BindJSON(&loginBody); err != nil { if err := c.BindJSON(&loginBody); err != nil {
fmt.Println(err) fmt.Println(err)
return return
} }
loggedIn, token := storage.CheckLogin(loginBody.Username, loginBody.Password, c.ClientIP()) userAgentString := c.GetHeader("User-Agent")
if userAgentString == "" {
return
}
ua := storage.ParseUA(userAgentString)
loggedIn, token := storage.CheckLoginPassword(loginBody.Username, loginBody.Password, c.ClientIP(), ua)
if loggedIn { if loggedIn {
c.JSON(200, gin.H{ c.JSON(200, gin.H{
@@ -32,8 +43,35 @@ func login(c *gin.Context) {
} }
} }
func loginToken(c *gin.Context) {
var loginBody LoginTokenBody
if err := c.BindJSON(&loginBody); err != nil {
fmt.Println(err)
return
}
userAgentString := c.GetHeader("User-Agent")
if userAgentString == "" {
return
}
ua := storage.ParseUA(userAgentString)
loggedIn := storage.CheckLoginToken(loginBody.Token, c.ClientIP(), ua)
if loggedIn {
c.JSON(200, gin.H{
"token": loginBody.Token,
})
} else {
c.JSON(401, gin.H{
"error": "invalid username or password",
})
}
}
func updateLogin(c *gin.Context) { func updateLogin(c *gin.Context) {
var updateLogin LoginBody var updateLogin LoginPasswordBody
if err := c.BindJSON(&updateLogin); err != nil { if err := c.BindJSON(&updateLogin); err != nil {
fmt.Println(err) fmt.Println(err)
return return

1
go.mod
View File

@@ -19,6 +19,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mileusna/useragent v1.2.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect

2
go.sum
View File

@@ -44,6 +44,8 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mileusna/useragent v1.2.1 h1:p3RJWhi3LfuI6BHdddojREyK3p6qX67vIfOVMnUIVr0=
github.com/mileusna/useragent v1.2.1/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=

View File

@@ -6,10 +6,41 @@ import (
"time" "time"
"git.zomo.dev/zomo/discord-retokenizer/util" "git.zomo.dev/zomo/discord-retokenizer/util"
"github.com/go-redis/redis/v9" "github.com/mileusna/useragent"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type UserAgentSimple struct {
Name string `json:"name"`
Version string `json:"version"`
OS string `json:"os"`
OSVersion string `json:"os_version"`
Mobile bool `json:"mobile"`
Tablet bool `json:"tablet"`
Desktop bool `json:"desktop"`
}
func (ua UserAgentSimple) Compare(ua2 UserAgentSimple) bool {
return ua.Name == ua2.Name &&
ua.OS == ua2.OS &&
ua.Mobile == ua2.Mobile &&
ua.Tablet == ua2.Tablet &&
ua.Desktop == ua2.Desktop
}
func ParseUA(userAgentString string) UserAgentSimple {
ua := useragent.Parse(userAgentString)
return UserAgentSimple{
Name: ua.Name,
Version: ua.Version,
OS: ua.OS,
OSVersion: ua.OSVersion,
Mobile: ua.Mobile,
Tablet: ua.Tablet,
Desktop: ua.Desktop,
}
}
func UpdateUsername(username string) { func UpdateUsername(username string) {
if username != "" { if username != "" {
client.Set(ctx, "username", username, 0) client.Set(ctx, "username", username, 0)
@@ -26,7 +57,7 @@ func UpdatePassword(password string) {
} }
} }
func CheckLogin(username string, password string, ip string) (bool, string) { func CheckLoginPassword(username string, password string, ip string, userAgent UserAgentSimple) (bool, string) {
if username == "" || password == "" { if username == "" || password == "" {
return false, "" return false, ""
} }
@@ -50,14 +81,30 @@ func CheckLogin(username string, password string, ip string) (bool, string) {
return false, "" return false, ""
} }
return true, createLoginToken(ip) //return existing token if it exists
tokens := getLoginTokens()
fmt.Printf("There are %d tokens", len(tokens))
for _, token := range tokens {
// fmt.Printf("Checking token %s\n", token.ID)
// fmt.Printf("IP:\n Given: %s\n Token: %s\n", ip, token.IP)
// fmt.Printf("UA:\n Given: %+v\n Token: %+v\n", userAgent, token.UserAgent)
// fmt.Printf("Compare UA: %t\n", token.UserAgent.Compare(userAgent))
if token.IP == ip && userAgent.Compare(token.UserAgent) {
return true, token.Token
}
}
return true, createLoginToken(ip, userAgent)
} }
type LoginToken struct { type LoginToken struct {
ID string `json:"id"` ID string `json:"id"`
TokenHash string `json:"token"` Token string `json:"token"`
IP string `json:"ip"` IP string `json:"ip"`
End string `json:"end"` End string `json:"end"`
UserAgent UserAgentSimple `json:"user_agent"`
CreatedAt string `json:"created_at"`
LastLogin string `json:"last_login"`
} }
@@ -65,6 +112,9 @@ type LoginTokenSimple struct {
ID string `json:"id"` ID string `json:"id"`
IP string `json:"ip"` IP string `json:"ip"`
End string `json:"end"` End string `json:"end"`
UserAgent UserAgentSimple `json:"user_agent"`
CreatedAt string `json:"created_at"`
LastLogin string `json:"last_login"`
} }
func (t LoginToken) Simplify() LoginTokenSimple { func (t LoginToken) Simplify() LoginTokenSimple {
@@ -72,6 +122,9 @@ func (t LoginToken) Simplify() LoginTokenSimple {
ID: t.ID, ID: t.ID,
IP: t.IP, IP: t.IP,
End: t.End, End: t.End,
UserAgent: t.UserAgent,
CreatedAt: t.CreatedAt,
LastLogin: t.LastLogin,
} }
} }
@@ -83,27 +136,25 @@ func (t *LoginToken) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, t) return json.Unmarshal(data, t)
} }
func createLoginToken(ip string) string { func createLoginToken(ip string, ua UserAgentSimple) string {
token := util.GenerateToken() token := util.GenerateToken()
tokenHash, err := bcrypt.GenerateFromPassword([]byte(token), bcrypt.DefaultCost) tokenData := LoginToken{
ID: util.GenerateID(),
Token: token,
IP: ip,
End: util.GetEnd(token),
UserAgent: ua,
CreatedAt: time.Now().Format(time.RFC3339),
LastLogin: time.Now().Format(time.RFC3339),
}
err := client.RPush(ctx, "loginTokens", tokenData.ID).Err()
if err != nil { if err != nil {
panic(err) panic(err)
} }
tokenData := LoginToken{ err = client.Set(ctx, "loginToken:"+tokenData.ID, tokenData, 0).Err()
ID: util.GenerateID(),
TokenHash: string(tokenHash),
IP: ip,
End: util.GetEnd(token),
}
member := redis.Z{
Score: float64(time.Now().Unix() + 4 * 60 * 60),
Member: tokenData,
}
err = client.ZAdd(ctx, "loginTokens", member).Err()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -112,27 +163,30 @@ func createLoginToken(ip string) string {
} }
func getLoginTokens() []LoginToken { func getLoginTokens() []LoginToken {
expired, err := client.ZRangeByScore(ctx, "loginTokens", &redis.ZRangeBy{ var ids []string
Min: "-inf", err := client.LRange(ctx, "loginTokens", 0, -1).ScanSlice(&ids)
Max: fmt.Sprintf("%d", time.Now().Unix()),
}).Result()
if err != nil { if err != nil {
panic(err) panic(err)
} }
for _, e := range expired { var tokens []LoginToken
client.ZRem(ctx, "loginTokens", e) for _, id := range ids {
} var token LoginToken
err = client.Get(ctx, "loginToken:"+id).Scan(&token)
var current []LoginToken
err = client.ZRange(ctx, "loginTokens", 0, -1).ScanSlice(&current)
if err != nil { if err != nil {
panic(err) panic(err)
} }
tokens = append(tokens, token)
}
return tokens
}
return current func updateLastLoginToken(token LoginToken) {
token.CreatedAt = time.Now().Format(time.RFC3339)
err := client.Set(ctx, "loginToken:"+token.ID, token, 0).Err()
if err != nil {
panic(err)
}
} }
func GetLoginTokensSimple() []LoginTokenSimple { func GetLoginTokensSimple() []LoginTokenSimple {
@@ -146,15 +200,34 @@ func GetLoginTokensSimple() []LoginTokenSimple {
} }
func ClearLoginTokens() { func ClearLoginTokens() {
client.Del(ctx, "loginTokens") var ids []string
err := client.LRange(ctx, "loginTokens", 0, -1).ScanSlice(&ids)
if err != nil {
panic(err)
}
err = client.Del(ctx, "loginTokens").Err()
if err != nil {
panic(err)
}
for _, id := range ids {
err = client.Del(ctx, "loginToken:"+id).Err()
if err != nil {
panic(err)
}
}
} }
func CheckLoginToken(token string, ip string) bool { func CheckLoginToken(token string, ip string, userAgent UserAgentSimple) bool {
current := getLoginTokens() current := getLoginTokens()
for _, c := range current { for _, c := range current {
err := bcrypt.CompareHashAndPassword([]byte(c.TokenHash), []byte(token)) if token == c.Token && ip == c.IP && userAgent.Compare(c.UserAgent) {
if err == nil && ip == c.IP { fmt.Printf("Checking token %s\n", c.ID)
fmt.Printf("IP:\n Given: %s\n Token: %s\n", ip, c.IP)
fmt.Printf("UA:\n Given: %+v\n Token: %+v\n", userAgent, c.UserAgent)
updateLastLoginToken(c)
return true return true
} }
} }