mirror of
https://github.com/dnote/dnote
synced 2026-03-15 15:05:51 +01:00
Implement login
This commit is contained in:
parent
04ed337710
commit
6cb0e4e223
7 changed files with 164 additions and 59 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
12
pkg/server/database/errors.go
Normal file
12
pkg/server/database/errors.go
Normal file
|
|
@ -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")
|
||||
)
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
61
pkg/server/views/users/login.gohtml
Normal file
61
pkg/server/views/users/login.gohtml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{{define "yield"}}
|
||||
<div class="auth-page">
|
||||
<div class="container">
|
||||
<h1 class="heading">Sign in to Dnote</h1>
|
||||
|
||||
<div class="body">
|
||||
{{template "alert" .}}
|
||||
|
||||
<div class="panel">
|
||||
{{template "loginForm"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="callout">Don't have an account?</div>
|
||||
<a href="/register" class="cta">
|
||||
Create account
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{define "loginForm"}}
|
||||
<form action="/login" method="POST">
|
||||
{{csrfField}}
|
||||
|
||||
<div class="input-row">
|
||||
<label for="email-input" class="label">
|
||||
Email
|
||||
<input
|
||||
tabindex="1"
|
||||
id="email-input"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
class="form-control"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-row">
|
||||
<label for="password-input" class="label">
|
||||
Password
|
||||
<a href="/forgot" class="forgot">
|
||||
Forgot?
|
||||
</a>
|
||||
<input
|
||||
tabindex="2"
|
||||
id="password-input"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="●●●●●●●●"
|
||||
class="form-control"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button tabindex="3" type="submit" class="auth-button button button-normal button-stretch button-first">Log In</button>
|
||||
</form>
|
||||
{{end}}
|
||||
Loading…
Add table
Add a link
Reference in a new issue