feat: show user creation on dashboard

This commit is contained in:
Ruidy 2025-09-20 01:28:57 +02:00
parent f02d41901d
commit 3c7092236f
No known key found for this signature in database
GPG key ID: 705C24D202990805
7 changed files with 75 additions and 16 deletions

View file

@ -42,3 +42,12 @@ func (s *Service) Authenticate(ctx context.Context, email UserEmail, password st
return account, nil
}
// LookupByEmail fetches a user by canonical email.
func (s *Service) LookupByEmail(ctx context.Context, email UserEmail) (*User, error) {
if email.IsZero() {
return nil, ErrInvalidInput
}
return s.store.FindByEmail(ctx, email)
}

View file

@ -1,6 +1,14 @@
package server
import "net/http"
import (
"log"
"net/http"
"time"
"github.com/rjnemo/auth/internal/auth"
)
const dashboardTimeDisplayLayout = "02 Jan 2006 15:04 MST"
func (s *Server) dashboardHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@ -12,6 +20,23 @@ func (s *Server) dashboardHandler() http.HandlerFunc {
return
}
s.render(w, "in.html", PageData{Email: state.Email, CSRFToken: state.CSRFToken})
email, err := auth.NewUserEmail(state.Email)
if err != nil {
log.Printf("dashboard: invalid session email: %v", err)
http.Error(w, "session invalid", http.StatusUnauthorized)
return
}
account, err := s.authService.LookupByEmail(r.Context(), email)
if err != nil {
log.Printf("dashboard: lookup failed: %v", err)
http.Error(w, "unable to load account", http.StatusInternalServerError)
return
}
createdAtISO := account.CreatedAt.Format(time.RFC3339)
createdAtDisplay := account.CreatedAt.Format(dashboardTimeDisplayLayout)
s.render(w, "in.html", newDashboardData(state.Email, state.CSRFToken, createdAtDisplay, createdAtISO))
}
}

View file

@ -3,14 +3,13 @@ package server
import (
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"net/http"
"time"
)
const (
sessionCookieName = "auth_session"
csrfSessionKey = "csrf_token"
sessionLifetime = 12 * time.Hour
sessionSecretMinLength = 32
csrfTokenByteLength int = 32
@ -24,7 +23,7 @@ type SessionStore struct {
// NewSessionStore creates a cookie-backed session store.
func NewSessionStore(secret []byte) (*SessionStore, error) {
if len(secret) < sessionSecretMinLength {
return nil, errors.New("session secret must be at least 32 bytes")
return nil, fmt.Errorf("session secret must be at least %d bytes", sessionSecretMinLength)
}
// copy secret to avoid external mutation
buf := make([]byte, len(secret))

View file

@ -2,9 +2,11 @@ package server
// PageData contains fields shared by the templates for now.
type PageData struct {
Email string
Error string
CSRFToken string
Email string
Error string
CSRFToken string
CreatedAt string
CreatedAtISO string
}
func newIndexData(email, errMsg, token string) PageData {
@ -14,3 +16,7 @@ func newIndexData(email, errMsg, token string) PageData {
func newUnauthorizedData(errMsg, token string) PageData {
return PageData{Error: errMsg, CSRFToken: token}
}
func newDashboardData(email, token, createdAt, createdAtISO string) PageData {
return PageData{Email: email, CSRFToken: token, CreatedAt: createdAt, CreatedAtISO: createdAtISO}
}

View file

@ -12,12 +12,24 @@
<body>
<main class="container">
<h1>Welcome</h1>
<p>You are signed in as <strong>{{.Email}}</strong>.</p>
<p>This placeholder dashboard will evolve as we flesh out the auth flow.</p>
<form method="post" action="/logout">
<input type="hidden" name="_csrf" value="{{.CSRFToken}}" />
<button type="submit" class="secondary">Sign out</button>
</form>
<article>
<p>You are signed in as <strong>{{.Email}}</strong>.</p>
{{if .CreatedAt}}
<p>
Member since
<time datetime="{{.CreatedAtISO}}">{{.CreatedAt}}</time>.
</p>
{{end}}
<p>
This placeholder dashboard will evolve as we flesh out the auth flow.
</p>
<footer>
<form method="post" action="/logout">
<input type="hidden" name="_csrf" value="{{.CSRFToken}}" />
<button type="submit" class="secondary">Sign out</button>
</form>
</footer>
</article>
</main>
</body>
</html>

View file

@ -15,7 +15,12 @@
<p>Authenticate with the demo credentials below to view the dashboard.</p>
<article>
<header>Demo account</header>
<p><strong>Email:</strong> user@example.com<br /><strong>Password:</strong> password123</p>
<p>
<strong>Email:</strong> user@example.com<br /><strong>
Password:
</strong>
password123
</p>
</article>
{{if .Error}}
<article class="secondary">

View file

@ -12,7 +12,10 @@
<body>
<main class="container">
<h1>Unauthorized</h1>
<p>{{if .Error}}{{.Error}}{{else}}You do not have permission to view that page.{{end}}</p>
<p>
{{if .Error}}{{.Error}}{{else}}You do not have permission to view that
page.{{end}}
</p>
<a href="/" role="button">Back to safety</a>
</main>
</body>