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

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)
}