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