Add authentication, styling needs to be fixed
# Conflicts: # starter/ent/runtime.go # starter/go.mod # starter/go.sum # starter/routes/routing.go
This commit is contained in:
+144
-21
@@ -1,7 +1,15 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
. "git.gorlug.de/code/ersteller"
|
||||
"git.gorlug.de/code/ersteller/authentication"
|
||||
"git.gorlug.de/code/ersteller/starter/ent"
|
||||
"git.gorlug.de/code/ersteller/starter/ent/user"
|
||||
"github.com/gorilla/sessions"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
hx "maragu.dev/gomponents-htmx"
|
||||
|
||||
. "maragu.dev/gomponents"
|
||||
. "maragu.dev/gomponents/html"
|
||||
@@ -9,34 +17,55 @@ import (
|
||||
|
||||
const LoginPath = "/login"
|
||||
const LoginPathDe = "/anmelden"
|
||||
const LocalLoginPath = "/login/local"
|
||||
const LocalLoginPathDe = "/anmelden/lokal"
|
||||
|
||||
var loginTexts *LoginTexts
|
||||
|
||||
type LoginTexts struct {
|
||||
PageTitle I18nText
|
||||
PageDescription I18nText
|
||||
HeroTitle I18nText
|
||||
HeroDescription I18nText
|
||||
GoogleLoginBtn I18nText
|
||||
PageTitle I18nText
|
||||
PageDescription I18nText
|
||||
HeroTitle I18nText
|
||||
HeroDescription I18nText
|
||||
GoogleLoginBtn I18nText
|
||||
EmailLabel I18nText
|
||||
PasswordLabel I18nText
|
||||
LoginBtn I18nText
|
||||
OrSeparator I18nText
|
||||
InvalidCreds I18nText
|
||||
SessionSaveError I18nText
|
||||
}
|
||||
|
||||
type Page struct {
|
||||
createPage CreateHtmxPageFunc
|
||||
ViewRoute HtmxRoute
|
||||
createPage CreateHtmxPageFunc
|
||||
ViewRoute HtmxRoute
|
||||
db *ent.Client
|
||||
sessionStore *sessions.CookieStore
|
||||
localLoginRoute HtmxRoute
|
||||
}
|
||||
|
||||
func NewPage(createPage CreateHtmxPageFunc, server HtmxServer, path *ActivePath) *Page {
|
||||
func NewPage(createPage CreateHtmxPageFunc, server HtmxServer, path *ActivePath,
|
||||
db *ent.Client, sessionStore *sessions.CookieStore) *Page {
|
||||
if loginTexts == nil {
|
||||
createLoginTexts()
|
||||
}
|
||||
page := &Page{
|
||||
createPage: createPage,
|
||||
createPage: createPage,
|
||||
db: db,
|
||||
sessionStore: sessionStore,
|
||||
}
|
||||
page.ViewRoute = NewHtmxGetRoute(page.View, LanguagePaths{
|
||||
En: LoginPath,
|
||||
De: LoginPathDe,
|
||||
}).SetActivePath(path)
|
||||
page.ViewRoute.Add(server)
|
||||
|
||||
// Add POST route for local login
|
||||
page.localLoginRoute = NewHtmxPostRoute(page.HandleLocalLogin, LanguagePaths{
|
||||
En: LocalLoginPath,
|
||||
De: LocalLoginPathDe,
|
||||
})
|
||||
page.localLoginRoute.Add(server)
|
||||
return page
|
||||
}
|
||||
|
||||
@@ -62,6 +91,30 @@ func createLoginTexts() {
|
||||
En: "Sign in with Google",
|
||||
De: "Mit Google anmelden",
|
||||
}),
|
||||
EmailLabel: NewI18nText(map[Language]string{
|
||||
En: "Email",
|
||||
De: "E-Mail",
|
||||
}),
|
||||
PasswordLabel: NewI18nText(map[Language]string{
|
||||
En: "Password",
|
||||
De: "Passwort",
|
||||
}),
|
||||
LoginBtn: NewI18nText(map[Language]string{
|
||||
En: "Sign in",
|
||||
De: "Anmelden",
|
||||
}),
|
||||
OrSeparator: NewI18nText(map[Language]string{
|
||||
En: "OR",
|
||||
De: "ODER",
|
||||
}),
|
||||
InvalidCreds: NewI18nText(map[Language]string{
|
||||
En: "Invalid email or password",
|
||||
De: "Ungültige E-Mail oder Passwort",
|
||||
}),
|
||||
SessionSaveError: NewI18nText(map[Language]string{
|
||||
En: "Failed to save session",
|
||||
De: "Sitzung konnte nicht gespeichert werden",
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,25 +128,95 @@ func (p *Page) getMetaData() PageWebsiteMetaData {
|
||||
}
|
||||
|
||||
func (p *Page) View(c HtmxContext) {
|
||||
content := LoginContent(c.GetLanguage())
|
||||
content := p.LoginContent(c.GetLanguage(), "")
|
||||
p.createPage(c, p.getMetaData(), content)
|
||||
}
|
||||
|
||||
func LoginContent(language Language) Group {
|
||||
return []Node{
|
||||
func (p *Page) LoginContent(language Language, errorMsg string) Group {
|
||||
nodes := []Node{
|
||||
Div(Class("hero-section login-section"),
|
||||
H1(Class("hero-title"), Text(loginTexts.HeroTitle.FromLang(language))),
|
||||
P(Class("hero-description"), Text(loginTexts.HeroDescription.FromLang(language))),
|
||||
Div(Class("login-buttons"),
|
||||
A(
|
||||
Href("/login/google"),
|
||||
Button(
|
||||
Class("btn btn-primary google-login-btn"),
|
||||
Type("button"),
|
||||
Text(loginTexts.GoogleLoginBtn.FromLang(language)),
|
||||
),
|
||||
),
|
||||
}
|
||||
|
||||
if errorMsg != "" {
|
||||
nodes = append(nodes, Div(Class("error-message"), Text(errorMsg)))
|
||||
}
|
||||
|
||||
nodes = append(nodes,
|
||||
Form(
|
||||
p.localLoginRoute.GetHtmx(language),
|
||||
hx.Target("body"),
|
||||
Class("login-form"),
|
||||
Div(Class("form-group"),
|
||||
Label(For("email"), Text(loginTexts.EmailLabel.FromLang(language))),
|
||||
Input(Type("email"), ID("email"), Name("email"), Required(), Class("form-control")),
|
||||
),
|
||||
Div(Class("form-group"),
|
||||
Label(For("password"), Text(loginTexts.PasswordLabel.FromLang(language))),
|
||||
Input(Type("password"), ID("password"), Name("password"), Required(), Class("form-control")),
|
||||
),
|
||||
Button(
|
||||
Type("submit"),
|
||||
Class("btn btn-primary"),
|
||||
Text(loginTexts.LoginBtn.FromLang(language)),
|
||||
),
|
||||
),
|
||||
Div(Class("separator"), Text(loginTexts.OrSeparator.FromLang(language))),
|
||||
Div(Class("login-buttons"),
|
||||
A(
|
||||
Href("/login/google"),
|
||||
Button(
|
||||
Class("btn btn-primary google-login-btn"),
|
||||
Type("button"),
|
||||
Text(loginTexts.GoogleLoginBtn.FromLang(language)),
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (p *Page) HandleLocalLogin(c HtmxContext) {
|
||||
req := c.GetRequest()
|
||||
if err := req.ParseForm(); err != nil {
|
||||
content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage()))
|
||||
p.createPage(c, p.getMetaData(), content)
|
||||
return
|
||||
}
|
||||
|
||||
email := req.FormValue("email")
|
||||
password := req.FormValue("password")
|
||||
|
||||
// Find user by email
|
||||
ctx := req.Context()
|
||||
foundUser, err := p.db.User.Query().Where(user.EmailEQ(email)).Only(ctx)
|
||||
if err != nil {
|
||||
Error("could not find user ", email, "with error:", err)
|
||||
content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage()))
|
||||
p.createPage(c, p.getMetaData(), content)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify password
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(foundUser.PasswordHash), []byte(password)); err != nil {
|
||||
Error("could not verify password for ", email, "with error:", err)
|
||||
content := p.LoginContent(c.GetLanguage(), loginTexts.InvalidCreds.FromLang(c.GetLanguage()))
|
||||
p.createPage(c, p.getMetaData(), content)
|
||||
return
|
||||
}
|
||||
|
||||
// Set session
|
||||
err = authentication.SaveEmailAndUserIdToSessionStore(c.GetRequest(), c.GetResponseWriter(), p.sessionStore, foundUser.Email, foundUser.ID)
|
||||
if err != nil {
|
||||
Error("could not save user id for ", email, "to session store:", err)
|
||||
content := p.LoginContent(c.GetLanguage(), loginTexts.SessionSaveError.FromLang(c.GetLanguage()))
|
||||
p.createPage(c, p.getMetaData(), content)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to home page
|
||||
http.Redirect(c.GetResponseWriter(), req, "/", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user