You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
2.7 KiB
Go
137 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
// PublicUser is the "public" version of the "User" struct, this excludes private information
|
|
type PublicUser struct {
|
|
Username string `json:"username"`
|
|
}
|
|
|
|
// User represents a user in the database
|
|
type User struct {
|
|
Username string `json:"username"`
|
|
PasswordBCrypt []byte `json:"passwordBCrypt"`
|
|
}
|
|
|
|
func (u User) PublicUser() PublicUser {
|
|
return PublicUser{
|
|
Username: u.Username,
|
|
}
|
|
}
|
|
|
|
type Database interface {
|
|
GetUsers() ([]User, error)
|
|
GetUser(username string) (User, error)
|
|
CreateUser(user User) error
|
|
}
|
|
|
|
type memDB struct {
|
|
Users map[string]User `json:"users"`
|
|
}
|
|
|
|
func NewInMemoryDB() (Database, error) {
|
|
var db memDB
|
|
|
|
exampleFile, err := os.Open("example-db.local.json")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := json.NewDecoder(exampleFile).Decode(&db); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &db, nil
|
|
}
|
|
|
|
func (db *memDB) GetUsers() ([]User, error) {
|
|
users := make([]User, 0, len(db.Users))
|
|
for _, u := range db.Users {
|
|
users = append(users, u)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (db *memDB) GetUser(username string) (User, error) {
|
|
user, ok := db.Users[username]
|
|
if !ok {
|
|
return User{}, fmt.Errorf(`no user with username %q`, username)
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (db *memDB) CreateUser(user User) error {
|
|
if _, ok := db.Users[user.Username]; ok {
|
|
return fmt.Errorf(`user with username %q already exists`, user.Username)
|
|
}
|
|
|
|
db.Users[user.Username] = user
|
|
return nil
|
|
}
|
|
|
|
type Auth interface {
|
|
Register(username, password string) error
|
|
Login(username, password string) (string, error)
|
|
UserForSession(token string) (string, error)
|
|
GetUser(username string) (User, error)
|
|
}
|
|
|
|
type memAuth struct {
|
|
db Database
|
|
|
|
sessions map[string]string
|
|
}
|
|
|
|
func NewInMemoryAuth(db Database) Auth {
|
|
return &memAuth{db, map[string]string{}}
|
|
}
|
|
|
|
func (auth *memAuth) Register(username, password string) error {
|
|
passwordBCrypt, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return auth.db.CreateUser(User{
|
|
Username: username,
|
|
PasswordBCrypt: passwordBCrypt,
|
|
})
|
|
}
|
|
|
|
func (auth *memAuth) Login(username, password string) (string, error) {
|
|
user, err := auth.db.GetUser(username)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if err := bcrypt.CompareHashAndPassword(user.PasswordBCrypt, []byte(password)); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
token := GenerateRandomString(16)
|
|
auth.sessions[token] = username
|
|
|
|
return token, nil
|
|
}
|
|
|
|
func (auth *memAuth) UserForSession(token string) (string, error) {
|
|
username, ok := auth.sessions[token]
|
|
if !ok {
|
|
return "", fmt.Errorf(`invalid session token`)
|
|
}
|
|
|
|
return username, nil
|
|
}
|
|
|
|
func (auth *memAuth) GetUser(username string) (User, error) {
|
|
return auth.db.GetUser(username)
|
|
}
|