diff --git a/pkg/server/app/users.go b/pkg/server/app/users.go index ca902775..3a4cad46 100644 --- a/pkg/server/app/users.go +++ b/pkg/server/app/users.go @@ -20,6 +20,7 @@ package app import ( "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/log" "github.com/dnote/dnote/pkg/server/token" "github.com/jinzhu/gorm" "github.com/pkg/errors" @@ -99,3 +100,42 @@ func (a *App) CreateUser(email, password string) (database.User, error) { return user, nil } + +// Authenticate authenticates a user +func (a *App) Authenticate(email, password string) (*database.User, error) { + var account database.Account + conn := a.DB.Debug().Where("email = ?", email).First(&account) + if conn.RecordNotFound() { + return nil, errors.New("not found") + } else if conn.Error != nil { + return nil, conn.Error + } + + err := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte(password)) + if err != nil { + return nil, err + } + + var user database.User + err = a.DB.Where("id = ?", account.UserID).First(&user).Error + if err != nil { + return nil, errors.Wrap(err, "finding user") + } + + return &user, nil +} + +// SignIn signs in a user +func (a *App) SignIn(user *database.User) (*database.Session, error) { + err := a.TouchLastLoginAt(*user, a.DB) + if err != nil { + log.ErrorWrap(err, "touching login timestamp") + } + + session, err := a.CreateSession(user.ID) + if err != nil { + return nil, errors.Wrap(err, "creating session") + } + + return &session, nil +} diff --git a/pkg/server/controllers/controllers.go b/pkg/server/controllers/controllers.go index 7aeb67ca..df291dea 100644 --- a/pkg/server/controllers/controllers.go +++ b/pkg/server/controllers/controllers.go @@ -15,7 +15,7 @@ type Controllers struct { func New(cfg config.Config, app *app.App) *Controllers { c := Controllers{} - c.Users = NewUsers(cfg, app.DB) + c.Users = NewUsers(cfg, app) c.Static = NewStatic(cfg) return &c diff --git a/pkg/server/controllers/users.go b/pkg/server/controllers/users.go index 7e9eaa60..5e215191 100644 --- a/pkg/server/controllers/users.go +++ b/pkg/server/controllers/users.go @@ -1,27 +1,31 @@ package controllers import ( + "encoding/json" "net/http" + "github.com/dnote/dnote/pkg/server/app" "github.com/dnote/dnote/pkg/server/config" + "github.com/dnote/dnote/pkg/server/log" "github.com/dnote/dnote/pkg/server/views" - "github.com/jinzhu/gorm" ) // NewUsers creates a new Users controller. // It panics if the necessary templates are not parsed. -func NewUsers(cfg config.Config, db *gorm.DB) *Users { +func NewUsers(cfg config.Config, app *app.App) *Users { return &Users{ NewView: views.NewView(cfg.PageTemplateDir, views.Config{Title: "Join", Layout: "base"}, "users/new"), + LoginView: views.NewView(cfg.PageTemplateDir, views.Config{Title: "Login", Layout: "base"}, "users/login"), onPremise: cfg.OnPremise, - db: db, + app: app, } } // Users is a user controller. type Users struct { NewView *views.View - db *gorm.DB + LoginView *views.View + app *app.App onPremise bool } @@ -43,3 +47,41 @@ type LoginForm struct { Email string `schema:"email" json:"email"` Password string `schema:"password" json:"password"` } + +func (u *Users) Login(w http.ResponseWriter, r *http.Request) { + var form LoginForm + if err := parseRequestData(r, &form); err != nil { + log.Error(err.Error()) + w.WriteHeader(500) + return + } + + user, err := u.app.Authenticate(form.Email, form.Password) + if err != nil { + log.Error(err.Error()) + w.WriteHeader(500) + return + } + + session, err := u.app.SignIn(user) + if err != nil { + log.Error(err.Error()) + w.WriteHeader(500) + return + } + + setSessionCookie(w, session.Key, session.ExpiresAt) + + response := SessionResponse{ + Key: session.Key, + ExpiresAt: session.ExpiresAt.Unix(), + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + if err := json.NewEncoder(w).Encode(response); err != nil { + log.Error(err.Error()) + return + } + +} diff --git a/pkg/server/database/errors.go b/pkg/server/database/errors.go new file mode 100644 index 00000000..e142a83d --- /dev/null +++ b/pkg/server/database/errors.go @@ -0,0 +1,12 @@ +package database + +import ( + "github.com/pkg/errors" +) + +type modelError string + +var ( + // ErrNotFound an error that indicates that the given resource is not found + ErrNotFound error = errors.New("not found") +) diff --git a/pkg/server/main.go b/pkg/server/main.go index 9dcca8e5..d26f8283 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -25,7 +25,6 @@ import ( "net/http" "github.com/dnote/dnote/pkg/clock" - "github.com/dnote/dnote/pkg/server/api" "github.com/dnote/dnote/pkg/server/app" "github.com/dnote/dnote/pkg/server/config" "github.com/dnote/dnote/pkg/server/controllers" @@ -33,67 +32,17 @@ import ( "github.com/dnote/dnote/pkg/server/job" "github.com/dnote/dnote/pkg/server/mailer" "github.com/dnote/dnote/pkg/server/routes" - "github.com/dnote/dnote/pkg/server/web" "github.com/jinzhu/gorm" - "github.com/gobuffalo/packr/v2" "github.com/pkg/errors" ) var versionTag = "master" var port = flag.String("port", "3000", "port to connect to") -var rootBox *packr.Box var pageDir = flag.String("pageDir", "views", "the path to a directory containing page templates") var staticDir = flag.String("staticDir", "./static/", "the path to the static directory ") -func init() { - rootBox = packr.New("root", "../../web/public") -} - -func mustFind(box *packr.Box, path string) []byte { - b, err := rootBox.Find(path) - if err != nil { - panic(errors.Wrapf(err, "getting file content for %s", path)) - } - - return b -} - -func initWebContext(db *gorm.DB) web.Context { - staticBox := packr.New("static", "../../web/public/static") - - return web.Context{ - DB: db, - IndexHTML: mustFind(rootBox, "index.html"), - RobotsTxt: mustFind(rootBox, "robots.txt"), - ServiceWorkerJs: mustFind(rootBox, "service-worker.js"), - StaticFileSystem: staticBox, - } -} - -func initServer(a app.App) (*http.ServeMux, error) { - apiRouter, err := api.NewRouter(&api.API{App: &a}) - if err != nil { - return nil, errors.Wrap(err, "initializing router") - } - - webCtx := initWebContext(a.DB) - webHandlers, err := web.Init(webCtx) - if err != nil { - return nil, errors.Wrap(err, "initializing web handlers") - } - - mux := http.NewServeMux() - mux.Handle("/api/", http.StripPrefix("/api", apiRouter)) - mux.Handle("/static/", webHandlers.GetStatic) - mux.HandleFunc("/service-worker.js", webHandlers.GetServiceWorker) - mux.HandleFunc("/robots.txt", webHandlers.GetRobots) - mux.HandleFunc("/", webHandlers.GetRoot) - - return mux, nil -} - func initDB(c config.Config) *gorm.DB { db, err := gorm.Open("postgres", c.DB.GetConnectionStr()) if err != nil { @@ -152,7 +101,7 @@ func startCmd() { r := routes.New(&app, rc) - log.Printf("Dnote version %s is running on port %s", versionTag, *port) + log.Printf("dnote version %s is running on port %s", versionTag, *port) log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%s", cfg.Port), r)) } @@ -161,7 +110,7 @@ func versionCmd() { } func rootCmd() { - fmt.Printf(`Dnote Server - A simple personal knowledge base + fmt.Printf(`dnote server - a simple personal knowledge base Usage: dnote-server [command] diff --git a/pkg/server/routes/routes.go b/pkg/server/routes/routes.go index e702508b..75dca359 100644 --- a/pkg/server/routes/routes.go +++ b/pkg/server/routes/routes.go @@ -31,7 +31,8 @@ func NewWebRoutes(app *app.App, c *controllers.Controllers) []Route { return []Route{ {"GET", "/", Auth(app, http.HandlerFunc(c.Users.New), &AuthParams{RedirectGuestsToLogin: true}), true}, {"GET", "/new", http.HandlerFunc(c.Users.New), true}, - {"GET", "/login", http.HandlerFunc(c.Users.New), true}, + {"GET", "/login", c.Users.LoginView, true}, + {"POST", "/login", http.HandlerFunc(c.Users.Login), true}, } } diff --git a/pkg/server/views/users/login.gohtml b/pkg/server/views/users/login.gohtml new file mode 100644 index 00000000..cad8debc --- /dev/null +++ b/pkg/server/views/users/login.gohtml @@ -0,0 +1,61 @@ +{{define "yield"}} +