feat: set auth cookie dl in regular res. cookie
This commit is contained in:
parent
bf3e40eae0
commit
eb3c3b7a24
@ -25,6 +25,10 @@ import (
|
|||||||
const (
|
const (
|
||||||
accessTokenDuration = 15 * time.Minute
|
accessTokenDuration = 15 * time.Minute
|
||||||
refreshTokenDuration = 7 * 24 * time.Hour
|
refreshTokenDuration = 7 * 24 * time.Hour
|
||||||
|
|
||||||
|
authCookieName = "notatest.refresh_token"
|
||||||
|
viewCookieName = "notatest.expires_at"
|
||||||
|
authCookiePath = "/api/auth/cookie"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -222,16 +226,7 @@ func (rs authResource) Login(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set refresh token into a httpOnly cookie
|
rs.setAuthCookies(w, tokenPair, false)
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "notatest.refresh_token",
|
|
||||||
Value: tokenPair.RefreshToken,
|
|
||||||
Path: "/api/auth/cookie",
|
|
||||||
MaxAge: int(refreshTokenDuration.Seconds()),
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: rs.Config.IsProd,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Build response
|
// Build response
|
||||||
response := map[string]any{
|
response := map[string]any{
|
||||||
@ -327,16 +322,7 @@ func (rs authResource) UpdatePassword(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set refresh token into a httpOnly cookie
|
rs.setAuthCookies(w, tokenPair, false)
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "notatest.refresh_token",
|
|
||||||
Value: tokenPair.RefreshToken,
|
|
||||||
Path: "/api/auth/cookie",
|
|
||||||
MaxAge: int(refreshTokenDuration.Seconds()),
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: rs.Config.IsProd,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
})
|
|
||||||
|
|
||||||
response := map[string]any{
|
response := map[string]any{
|
||||||
"access_token": tokenPair.AccessToken,
|
"access_token": tokenPair.AccessToken,
|
||||||
@ -383,15 +369,7 @@ func (rs authResource) OwnerDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear the refresh token cookie
|
// Clear the refresh token cookie
|
||||||
http.SetCookie(w, &http.Cookie{
|
rs.setAuthCookies(w, nil, true)
|
||||||
Name: "notatest.refresh_token",
|
|
||||||
Value: "",
|
|
||||||
Path: "/api/auth/cookie",
|
|
||||||
MaxAge: 0, // Expires immediately
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: rs.Config.IsProd,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := rs.Users.RevokeAllUserRefreshTokens(r.Context(), user.ID); err != nil {
|
if err := rs.Users.RevokeAllUserRefreshTokens(r.Context(), user.ID); err != nil {
|
||||||
log.Error().Msgf("Failed to revoke refresh tokens: %s", err)
|
log.Error().Msgf("Failed to revoke refresh tokens: %s", err)
|
||||||
@ -555,16 +533,7 @@ func (rs authResource) RefreshAccessToken(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set refresh token into a httpOnly cookie
|
rs.setAuthCookies(w, tokenPair, false)
|
||||||
http.SetCookie(w, &http.Cookie{
|
|
||||||
Name: "notatest.refresh_token",
|
|
||||||
Value: tokenPair.RefreshToken,
|
|
||||||
Path: "/api/auth/cookie",
|
|
||||||
MaxAge: int(refreshTokenDuration.Seconds()),
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: rs.Config.IsProd,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Return the access token in the response body (it should be stored in browser's memory client-side)
|
// Return the access token in the response body (it should be stored in browser's memory client-side)
|
||||||
respondJSON(w, http.StatusOK, map[string]string{
|
respondJSON(w, http.StatusOK, map[string]string{
|
||||||
@ -590,15 +559,7 @@ func (rs authResource) Logout(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear the refresh token cookie
|
// Clear the refresh token cookie
|
||||||
http.SetCookie(w, &http.Cookie{
|
rs.setAuthCookies(w, nil, true)
|
||||||
Name: "notatest.refresh_token",
|
|
||||||
Value: "",
|
|
||||||
Path: "/api/auth/cookie",
|
|
||||||
MaxAge: 0, // Expires immediately
|
|
||||||
HttpOnly: true,
|
|
||||||
Secure: rs.Config.IsProd,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := rs.Tokens.RevokeAllUserRefreshTokens(r.Context(), userID); err != nil {
|
if err := rs.Tokens.RevokeAllUserRefreshTokens(r.Context(), userID); err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, "Failed to logout")
|
respondError(w, http.StatusInternalServerError, "Failed to logout")
|
||||||
@ -632,6 +593,48 @@ func (rs authResource) userFromCtxClaims(w http.ResponseWriter, r *http.Request)
|
|||||||
return &user
|
return &user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rs authResource) setAuthCookies(w http.ResponseWriter, tokenPair *tokenPair, clearCookies bool) {
|
||||||
|
expirationTime := time.Now().Add(refreshTokenDuration)
|
||||||
|
expirationUnix := strconv.FormatInt(expirationTime.Unix(), 10)
|
||||||
|
|
||||||
|
log.Debug().Msgf("Setting authentication cookies (clearCookies: %t)", clearCookies)
|
||||||
|
|
||||||
|
var maxAge int
|
||||||
|
var value string
|
||||||
|
if clearCookies {
|
||||||
|
maxAge = 0 // Expires immediately
|
||||||
|
value = ""
|
||||||
|
} else {
|
||||||
|
maxAge = int(refreshTokenDuration.Seconds())
|
||||||
|
value = tokenPair.RefreshToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actual auth cookie is httpOnly, i.e. not viewable by the client
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: authCookieName,
|
||||||
|
Value: value,
|
||||||
|
Domain: rs.Config.Domain,
|
||||||
|
Path: authCookiePath,
|
||||||
|
MaxAge: maxAge,
|
||||||
|
HttpOnly: true,
|
||||||
|
Secure: rs.Config.IsProd,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
})
|
||||||
|
|
||||||
|
// The information cookie can be used by the client to check how long it'll take until the
|
||||||
|
// actual auth cookie expires (notably `HttpOnly: false` is a must)
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: viewCookieName,
|
||||||
|
Value: expirationUnix,
|
||||||
|
Domain: rs.Config.Domain,
|
||||||
|
Path: authCookiePath,
|
||||||
|
MaxAge: maxAge,
|
||||||
|
HttpOnly: false,
|
||||||
|
Secure: rs.Config.IsProd,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function for generating the initial administrator level account if one doesn't already
|
// Helper function for generating the initial administrator level account if one doesn't already
|
||||||
// exists in the database.
|
// exists in the database.
|
||||||
func CreateAdminIfNotExists(ctx context.Context, q *data.Queries, username, password string) error {
|
func CreateAdminIfNotExists(ctx context.Context, q *data.Queries, username, password string) error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user