2023-07-26 10:08:59 +00:00
|
|
|
/*
|
|
|
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
sessionCookie = "camper-session"
|
|
|
|
)
|
|
|
|
|
|
|
|
func SetSessionCookie(w http.ResponseWriter, cookie string) {
|
|
|
|
http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour))
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetSessionCookie(r *http.Request) string {
|
|
|
|
if cookie, err := r.Cookie(sessionCookie); err == nil {
|
|
|
|
return cookie.Value
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
Add the logout button
Conceptually, to logout we have to “delete the session”, thus the best
HTTP verb would be `DELETE`. However, there is no way to send a
`DELETE` request with a regular HTML form, and it seems that never will
be[0].
I could use a POST, optionally with a “method override” technique, but
i was planing to use HTMx anyway, so this was as good an opportunity to
include it as any.
In this application i am not concerned with people not having JavaScript
enabled, because it is for a customer that has a known environment, and
we do not have much time anyway. Therefore, i opted to forgo
progressive enhancement in cases like this: if `DELETE` is needed, use
`hx-delete`.
Unfortunately, i can not use a <form> with a hidden <input> for the
CSRF token, because `DELETE` requests do not have body and the value
should be added as query parameters, like a form with GET method, but
HTMx does the incorrect thing here: sends the values in the request’s
body. That’s why i have to use a custom header and the `hx-header`
directive to include the CSRF token.
Then, by default HTMx targets the triggered element for swap with the
response from the server, but after a logout i want to redirect the
user to the login form again. I could set the hx-target to button to
replace the whole body, or tell the client to redirect to the new
location. I actually do not know which one is “better”. Maybe the
hx-target is best because then everything is handled by the client, but
in the case of logout, since it is possible that i might want to load
scripts only for logged-in users in the future, i opted for the full
page reload.
However, HTMx does not want to reload a page that return HTTP 401,
hence i had to include the GET method to /login in order to return the
login form with a response of HTTP 200, which also helps when
reloading in the browser after a failed login attempt. I am not worried
with the HTTP 401 when attempting to load a page as guest, because
this request most probably comes from the browser, not HTMx, and it will
show the login form as intended—even though it is not compliant, since
it does not return the WWW-Authenticate header, but this is the best i
can do given that no cookie-based authentication method has been
accepted[1].
[0]: https://www.w3.org/Bugs/Public/show_bug.cgi?id=10671#c16
[1]: https://datatracker.ietf.org/doc/id/draft-broyer-http-cookie-auth-00.html
2023-07-26 11:49:47 +00:00
|
|
|
func DeleteSessionCookie(w http.ResponseWriter) {
|
|
|
|
http.SetCookie(w, createSessionCookie("", -24*time.Hour))
|
|
|
|
}
|
|
|
|
|
2023-07-26 10:08:59 +00:00
|
|
|
func createSessionCookie(value string, duration time.Duration) *http.Cookie {
|
|
|
|
return &http.Cookie{
|
|
|
|
Name: sessionCookie,
|
|
|
|
Value: value,
|
|
|
|
Path: "/",
|
|
|
|
Expires: time.Now().Add(duration),
|
|
|
|
HttpOnly: true,
|
|
|
|
SameSite: http.SameSiteLaxMode,
|
|
|
|
}
|
|
|
|
}
|