@@ -6,13 +6,16 @@ import (
66 "crypto/sha256"
77 "encoding/hex"
88 "fmt"
9+ "log/slog"
910 "net/http"
1011 "net/mail"
1112 "strings"
1213 "time"
1314 "unicode"
1415
1516 authpkg "github.com/pixelvide/aegis/server/internal/auth"
17+ "github.com/pixelvide/aegis/server/internal/config"
18+ "github.com/pixelvide/aegis/server/internal/email/templates"
1619 "github.com/pixelvide/aegis/server/internal/middleware"
1720 "github.com/pixelvide/aegis/server/internal/models"
1821 "github.com/pixelvide/aegis/server/internal/store"
@@ -88,9 +91,11 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
8891 PasswordHash : hash ,
8992 }
9093 if err := s .common .CreateUser (r .Context (), user ); err != nil {
94+ slog .Error ("failed to create user" , "email" , req .Email , "error" , err )
9195 writeError (w , http .StatusInternalServerError , "failed to create user" )
9296 return
9397 }
98+ slog .Info ("user registered" , "user_id" , user .ID , "email" , req .Email )
9499
95100 // Create primary email entry + send verification
96101 primaryEmail , err := s .common .AddPrimaryUserEmail (r .Context (), user .ID , req .Email )
@@ -128,7 +133,7 @@ func (s *Server) handleRegister(w http.ResponseWriter, r *http.Request) {
128133 }
129134
130135 s .createSession (r , user .ID , jti )
131- setAuthCookie (w , token )
136+ setAuthCookie (w , token , s . config )
132137
133138 writeJSON (w , http .StatusCreated , map [string ]any {
134139 "user" : user ,
@@ -215,7 +220,7 @@ issueToken:
215220 }
216221
217222 s .createSession (r , user .ID , jti )
218- setAuthCookie (w , token )
223+ setAuthCookie (w , token , s . config )
219224
220225 // Send login notification email
221226 ip := extractIP (r )
@@ -242,6 +247,7 @@ func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {
242247 SameSite : http .SameSiteLaxMode ,
243248 Path : "/" ,
244249 MaxAge : - 1 , // delete immediately
250+ Domain : cookieDomain (s .config ),
245251 })
246252 writeJSON (w , http .StatusOK , map [string ]string {"message" : "logged out" })
247253}
@@ -270,18 +276,32 @@ func (s *Server) handleMe(w http.ResponseWriter, r *http.Request) {
270276
271277// ─── Helpers ────────────────────────────────────────────────────────────────
272278
273- func setAuthCookie (w http.ResponseWriter , token string ) {
279+ func setAuthCookie (w http.ResponseWriter , token string , cfg * config. Config ) {
274280 http .SetCookie (w , & http.Cookie {
275281 Name : middleware .CookieName ,
276282 Value : token ,
277283 HttpOnly : true ,
278- // Secure: true, // enable in production with HTTPS
279284 SameSite : http .SameSiteLaxMode ,
280285 Path : "/" ,
281286 MaxAge : 86400 , // 24 hours
287+ // When BaseDomain is set, scope cookie to parent domain for cross-subdomain sharing
288+ Domain : cookieDomain (cfg ),
289+ // TODO(security): Enable Secure: true in production with HTTPS
290+ // TODO(security): Consider __Secure- cookie prefix when Secure is enabled
282291 })
283292}
284293
294+ // cookieDomain returns the domain to set on auth cookies.
295+ // When BaseDomain is set, returns ".aegis.io" (leading dot) so the cookie
296+ // is shared across all subdomains. When empty, returns "" (browser defaults
297+ // to the exact host — standard dev mode behavior).
298+ func cookieDomain (cfg * config.Config ) string {
299+ if cfg .BaseDomain != "" {
300+ return "." + cfg .BaseDomain
301+ }
302+ return ""
303+ }
304+
285305// createSession creates a session row for the given user and JTI.
286306func (s * Server ) createSession (r * http.Request , userID , jti string ) {
287307 ua := r .UserAgent ()
@@ -373,24 +393,12 @@ func parseUserAgent(ua string) (browser, os, deviceType string) {
373393}
374394
375395// sendLoginNotification sends an email alert about a new login.
376- func (s * Server ) sendLoginNotification (email , name , ip , browser , os , deviceType string ) {
396+ func (s * Server ) sendLoginNotification (emailAddr , name , ip , browser , os , deviceType string ) {
377397 loginTime := time .Now ().UTC ().Format ("Jan 02, 2006 at 15:04 UTC" )
378- subject := "Aegis — New sign-in to your account"
379- body := fmt .Sprintf (`<h2>New Sign-In Detected</h2>
380- <p>Hi %s,</p>
381- <p>We noticed a new sign-in to your Aegis account:</p>
382- <table style="border-collapse:collapse;margin:16px 0;">
383- <tr><td style="padding:4px 16px 4px 0;color:#666;">Browser</td><td style="padding:4px 0;">%s</td></tr>
384- <tr><td style="padding:4px 16px 4px 0;color:#666;">Operating System</td><td style="padding:4px 0;">%s</td></tr>
385- <tr><td style="padding:4px 16px 4px 0;color:#666;">Device</td><td style="padding:4px 0;">%s</td></tr>
386- <tr><td style="padding:4px 16px 4px 0;color:#666;">IP Address</td><td style="padding:4px 0;">%s</td></tr>
387- <tr><td style="padding:4px 16px 4px 0;color:#666;">Time</td><td style="padding:4px 0;">%s</td></tr>
388- </table>
389- <p>If this was you, no action is needed.</p>
390- <p>If you don't recognize this activity, please <strong>change your password immediately</strong> and review your active sessions.</p>
391- <p style="color:#666;font-size:12px;">Aegis Security Platform</p>
392- ` , name , browser , os , deviceType , ip , loginTime )
393- _ = s .email .Send (email , subject , body )
398+ subject , body := templates .LoginAlert (name , ip , browser , os , deviceType , loginTime )
399+ if err := s .email .Send (emailAddr , subject , body ); err != nil {
400+ slog .Error ("failed to send login notification email" , "email" , emailAddr , "error" , err )
401+ }
394402}
395403
396404// maskEmail masks an email for privacy (e.g., "j***@example.com").
@@ -435,19 +443,22 @@ func (s *Server) sendVerificationEmail(userID, emailID, emailAddr string) {
435443
436444 tokenBytes := make ([]byte , 32 )
437445 if _ , err := rand .Read (tokenBytes ); err != nil {
446+ slog .Error ("failed to generate verification token" , "user_id" , userID , "email" , emailAddr , "error" , err )
438447 return
439448 }
440449 token := hex .EncodeToString (tokenBytes )
441450 tokenHash := fmt .Sprintf ("%x" , sha256 .Sum256 ([]byte (token )))
442451 expiresAt := time .Now ().UTC ().Add (24 * time .Hour )
443452
444453 if err := s .common .CreatePasswordResetToken (ctx , userID , tokenHash , expiresAt ); err != nil {
454+ slog .Error ("failed to create verification token" , "user_id" , userID , "email" , emailAddr , "error" , err )
445455 return
446456 }
447457
448458 verifyURL := fmt .Sprintf ("%s/verify-email?token=%s&email_id=%s" , s .config .BaseURL , token , emailID )
449- subject := "Verify your email address"
450- body := fmt .Sprintf ("Click the following link to verify your email:\n \n %s\n \n This link expires in 24 hours." , verifyURL )
459+ subject , body := templates .VerifyEmail (verifyURL )
451460
452- _ = s .email .Send (emailAddr , subject , body )
461+ if err := s .email .Send (emailAddr , subject , body ); err != nil {
462+ slog .Error ("failed to send verification email" , "user_id" , userID , "email" , emailAddr , "error" , err )
463+ }
453464}
0 commit comments