mirror of
https://github.com/teamhanko/hanko.git
synced 2025-10-27 06:06:54 +08:00
* Add endpoint to invalidate HTTP-Only cookie from the backend * Add methods to the UserClient SDK for logout * Remove session token fetch and add unit test for logout * Update public router to use JWT middleware * Add logout button to frontend. Route back to login page once logout is successful. * Add a logout failur event * Update logout logic in SDK * Remove unneeded endpoint from main.go * Update logoutlink reference * Fix request path; undo change in package order * Update common.css to incldue hanko-logout * feat(fronend-sdk): remove cookie during cross-domain operations * fix(frontend-sdk): No unauthorized error during logout, when the user is already logged out * feat(backend): Create an audit log entry when the user logs off * chore(frontend-sdk): re-generate jsdoc * fix: Adjust logout response codes and the corresponding frontend sdk error handling * chore(frontend-sdk): re-generate jsdoc * feat: add logout endpoint specification to the docs * Fix broken unit test * Remove logout button from elements * Add event listener on frontend to call the logout method from SDK * Rollback changes to SecuredContent on e2e tests * Update logout test on user * Update quickstart/public/assets/css/common.css Co-authored-by: bjoern-m <56024829+bjoern-m@users.noreply.github.com> --------- Co-authored-by: Björn Müller <bjoern.mueller@hanko.io> Co-authored-by: bjoern-m <56024829+bjoern-m@users.noreply.github.com>
129 lines
3.5 KiB
Go
129 lines
3.5 KiB
Go
package session
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/gofrs/uuid"
|
|
"github.com/lestrrat-go/jwx/v2/jwt"
|
|
"github.com/teamhanko/hanko/backend/config"
|
|
hankoJwk "github.com/teamhanko/hanko/backend/crypto/jwk"
|
|
hankoJwt "github.com/teamhanko/hanko/backend/crypto/jwt"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
type Manager interface {
|
|
GenerateJWT(uuid.UUID) (string, error)
|
|
Verify(string) (jwt.Token, error)
|
|
GenerateCookie(token string) (*http.Cookie, error)
|
|
DeleteCookie() (*http.Cookie, error)
|
|
}
|
|
|
|
// Manager is used to create and verify session JWTs
|
|
type manager struct {
|
|
jwtGenerator hankoJwt.Generator
|
|
sessionLength time.Duration
|
|
cookieConfig cookieConfig
|
|
}
|
|
|
|
type cookieConfig struct {
|
|
Domain string
|
|
HttpOnly bool
|
|
SameSite http.SameSite
|
|
Secure bool
|
|
}
|
|
|
|
// NewManager returns a new Manager which will be used to create and verify sessions JWTs
|
|
func NewManager(jwkManager hankoJwk.Manager, config config.Session) (Manager, error) {
|
|
signatureKey, err := jwkManager.GetSigningKey()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create session generator: %w", err)
|
|
}
|
|
verificationKeys, err := jwkManager.GetPublicKeys()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create session generator: %w", err)
|
|
}
|
|
g, err := hankoJwt.NewGenerator(signatureKey, verificationKeys)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create session generator: %w", err)
|
|
}
|
|
|
|
duration, _ := time.ParseDuration(config.Lifespan) // error can be ignored, value is checked in config validation
|
|
sameSite := http.SameSite(0)
|
|
switch config.Cookie.SameSite {
|
|
case "lax":
|
|
sameSite = http.SameSiteLaxMode
|
|
case "strict":
|
|
sameSite = http.SameSiteStrictMode
|
|
case "none":
|
|
sameSite = http.SameSiteNoneMode
|
|
default:
|
|
sameSite = http.SameSiteDefaultMode
|
|
}
|
|
return &manager{
|
|
jwtGenerator: g,
|
|
sessionLength: duration,
|
|
cookieConfig: cookieConfig{
|
|
Domain: config.Cookie.Domain,
|
|
HttpOnly: config.Cookie.HttpOnly,
|
|
SameSite: sameSite,
|
|
Secure: config.Cookie.Secure,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// GenerateJWT creates a new session JWT for the given user
|
|
func (g *manager) GenerateJWT(userId uuid.UUID) (string, error) {
|
|
issuedAt := time.Now()
|
|
expiration := issuedAt.Add(g.sessionLength)
|
|
|
|
token := jwt.New()
|
|
_ = token.Set(jwt.SubjectKey, userId.String())
|
|
_ = token.Set(jwt.IssuedAtKey, issuedAt)
|
|
_ = token.Set(jwt.ExpirationKey, expiration)
|
|
//_ = token.Set(jwt.AudienceKey, []string{"http://localhost"})
|
|
|
|
signed, err := g.jwtGenerator.Sign(token)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(signed), nil
|
|
}
|
|
|
|
// Verify verifies the given JWT and returns a parsed one if verification was successful
|
|
func (g *manager) Verify(token string) (jwt.Token, error) {
|
|
parsedToken, err := g.jwtGenerator.Verify([]byte(token))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to verify session token: %w", err)
|
|
}
|
|
|
|
return parsedToken, nil
|
|
}
|
|
|
|
// GenerateCookie creates a new session cookie for the given user
|
|
func (g *manager) GenerateCookie(token string) (*http.Cookie, error) {
|
|
return &http.Cookie{
|
|
Name: "hanko",
|
|
Value: token,
|
|
Domain: g.cookieConfig.Domain,
|
|
Path: "/",
|
|
Secure: g.cookieConfig.Secure,
|
|
HttpOnly: g.cookieConfig.HttpOnly,
|
|
SameSite: g.cookieConfig.SameSite,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteCookie returns a cookie that will expire the cookie on the frontend
|
|
func (g *manager) DeleteCookie() (*http.Cookie, error) {
|
|
return &http.Cookie{
|
|
Name: "hanko",
|
|
Value: "",
|
|
Domain: g.cookieConfig.Domain,
|
|
Path: "/",
|
|
Secure: g.cookieConfig.Secure,
|
|
HttpOnly: g.cookieConfig.HttpOnly,
|
|
SameSite: g.cookieConfig.SameSite,
|
|
MaxAge: -1,
|
|
}, nil
|
|
}
|