Skip to content

Commit

Permalink
#27: apply cookie name prefix by options [broken tests]
Browse files Browse the repository at this point in the history
  • Loading branch information
egregors committed Nov 23, 2024
1 parent dc75fdc commit f5fc2f1
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 59 deletions.
2 changes: 1 addition & 1 deletion _example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func main() {
UserSessionMaxAge: 24 * time.Hour,
},
passkey.WithLogger(l),
passkey.WithCookieMaxAge(60*time.Minute),
passkey.WithUserSessionMaxAge(60*time.Minute),
passkey.WithInsecureCookie(), // In order to support Safari on localhost. Do not use in production.
)
if err != nil {
Expand Down
49 changes: 33 additions & 16 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
)

func (p *Passkey) beginRegistration(w http.ResponseWriter, r *http.Request) {
// TODO: refactoring, make it line option chain
p.log.Infof(">>> begin registration")

var err error
Expand Down Expand Up @@ -56,7 +57,7 @@ func (p *Passkey) beginRegistration(w http.ResponseWriter, r *http.Request) {
p.log.Debugf("session saved: %s", t)

p.log.Debugf("setting cookie")
p.setSessionCookie(w, t)
p.setAuthSessionCookie(w, t)

// return the options generated with the session key
// options.publicKey contain our registration options
Expand All @@ -76,12 +77,12 @@ func (p *Passkey) finishRegistration(w http.ResponseWriter, r *http.Request) {
p.log.Debugf("cleaned up session cookie")
p.log.Debugf("<<< finish registration: done with error")
}
p.deleteSessionCookie(w)
p.deleteAuthSessionCookie(w)
}()

// Get the session key from cookie
p.log.Debugf("getting session id from cookie")
sid, err := r.Cookie(p.cookieSettings.Name)
sid, err := r.Cookie(p.cookieSettings.authSessionName)
if err != nil {
err = fmt.Errorf("can't get session id: %w", err)

Expand Down Expand Up @@ -170,7 +171,7 @@ func (p *Passkey) beginLogin(w http.ResponseWriter, r *http.Request) {
options, session, err := p.webAuthn.BeginLogin(user)
if err != nil {
err = fmt.Errorf("can't begin login: %w", err)
p.deleteSessionCookie(w)
p.deleteAuthSessionCookie(w)

return
}
Expand All @@ -186,7 +187,7 @@ func (p *Passkey) beginLogin(w http.ResponseWriter, r *http.Request) {
p.log.Debugf("session saved: %s", t)

p.log.Debugf("setting cookie")
p.setSessionCookie(w, t)
p.setAuthSessionCookie(w, t)

// return the options generated with the session key
// options.publicKey contain our registration options
Expand All @@ -209,7 +210,7 @@ func (p *Passkey) finishLogin(w http.ResponseWriter, r *http.Request) {

// Get the session key from cookie
p.log.Debugf("getting session id from cookie")
sid, err := r.Cookie(p.cookieSettings.Name)
sid, err := r.Cookie(p.cookieSettings.authSessionName)
if err != nil {
err = fmt.Errorf("can't get session id: %w", err)

Expand Down Expand Up @@ -266,7 +267,7 @@ func (p *Passkey) finishLogin(w http.ResponseWriter, r *http.Request) {
// Delete the login session data
p.log.Debugf("deleting session data and cookie")
p.authSessionStore.Delete(sid.Value)
p.deleteSessionCookie(w)
p.deleteAuthSessionCookie(w)

p.log.Debugf("try to save user session")
t, err := p.userSessionStore.Create(UserSessionData{
Expand All @@ -281,32 +282,48 @@ func (p *Passkey) finishLogin(w http.ResponseWriter, r *http.Request) {
p.log.Debugf("session saved: %s", t)

p.log.Debugf("setting cookie")
p.setSessionCookie(w, t)
p.setUserSessionCookie(w, t)

JSONResponse(w, "Login Success", http.StatusOK)

p.log.Infof("<<< finish login")
}

// setSessionCookie sets a cookie
func (p *Passkey) setSessionCookie(w http.ResponseWriter, value string) {
// FIXME: now we user maxAge setting from consumer. But we actually need to use it
// only for userSession. Looks like maxAge for authSession we must set strictly
func (p *Passkey) setAuthSessionCookie(w http.ResponseWriter, value string) {
p.setSessionCookie(
w,
p.cookieSettings.authSessionName,
value,
p.cookieSettings.authSessionMaxAge,
)
}

func (p *Passkey) setUserSessionCookie(w http.ResponseWriter, value string) {
p.setSessionCookie(
w,
p.cookieSettings.userSessionName,
value,
p.cookieSettings.userSessionMaxAge,
)
}

func (p *Passkey) setSessionCookie(w http.ResponseWriter, name, value string, maxAge time.Duration) {
http.SetCookie(w, &http.Cookie{
Name: p.cookieSettings.Name,
Name: name,
Value: value,
Path: p.cookieSettings.Path,
MaxAge: int(p.cookieSettings.MaxAge.Seconds()),
MaxAge: int(maxAge.Seconds()),
Secure: p.cookieSettings.Secure,
HttpOnly: p.cookieSettings.HttpOnly,
SameSite: p.cookieSettings.SameSite,
})
}

// deleteSessionCookie deletes a cookie
func (p *Passkey) deleteSessionCookie(w http.ResponseWriter) { //nolint:unparam // it's ok here
// TODO: make it universal: gelCookie(w, name)
func (p *Passkey) deleteAuthSessionCookie(w http.ResponseWriter) { //nolint:unparam // it's ok here
http.SetCookie(w, &http.Cookie{
Name: p.cookieSettings.Name,
Name: p.cookieSettings.authSessionName,
Value: "",
Expires: time.Unix(0, 0),
MaxAge: -1,
Expand Down
4 changes: 1 addition & 3 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,13 @@ import (
func (p *Passkey) Auth(userIDKey string, onSuccess, onFail http.HandlerFunc) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sid, err := r.Cookie(p.cookieSettings.Name)
sid, err := r.Cookie(p.cookieSettings.userSessionName)
if err != nil {
exec(onFail, w, r)

return
}

// FIXME: i shouldn't use registration \ authorization session store here
// it should be a separate store with a mach lighter session object
session, ok := p.userSessionStore.Get(sid.Value)
if !ok {
exec(onFail, w, r)
Expand Down
46 changes: 37 additions & 9 deletions options.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package passkey

import "time"
import (
"fmt"
"strings"
"time"
)

type Option func(*Passkey)

Expand All @@ -20,21 +24,45 @@ func WithInsecureCookie() Option {
}
}

// WithSessionCookieName sets the name of the session cookie.
func WithSessionCookieName(name string) Option {
// WithSessionCookieNamePrefix sets the prefix for names of the session cookies.
func WithSessionCookieNamePrefix(prefix string) Option {
return func(p *Passkey) {
if name != "" {
// TODO: it probably could be a name PREFIX
p.cookieSettings.Name = name
if prefix != "" {
p.cookieSettings.authSessionName = camelCaseConcat(prefix, p.cookieSettings.authSessionName)
p.cookieSettings.userSessionName = camelCaseConcat(prefix, p.cookieSettings.userSessionName)
}
}
}

// WithCookieMaxAge sets the max age of the session cookie.
func WithCookieMaxAge(maxAge time.Duration) Option {
// WithUserSessionMaxAge sets the max age of the user session cookie.
func WithUserSessionMaxAge(maxAge time.Duration) Option {
return func(p *Passkey) {
if maxAge > 0 {
p.cookieSettings.MaxAge = maxAge
p.cookieSettings.userSessionMaxAge = maxAge
}
}
}

func camelCaseConcat(ws ...string) string {
if len(ws) == 0 {
return ""
}
if len(ws) == 1 {
return ws[0]
}

sb := strings.Builder{}
sb.WriteString(strings.ToLower(ws[0]))

for i := 1; i < len(ws); i++ {
w := ws[i]
sb.WriteString(
fmt.Sprintf(
"%s%s",
string(strings.ToUpper(w)[0]),
strings.ToLower(w)[1:],
))
}

return sb.String()
}
52 changes: 26 additions & 26 deletions passkey.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package passkey

import (
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"time"
Expand All @@ -18,8 +16,11 @@ const (
pathLoginBegin = "/passkey/loginBegin"
pathLoginFinish = "/passkey/loginFinish"

defaultSessionCookieName = "sid"
defaultCookieMaxAge = 60 * time.Minute
defaultSessionNamePrefix = "pk"
defaultAuthSessionName = "asid"
defaultUserSessionName = "usid"
defaultAuthSessionMaxAge = 5 * time.Minute
defaultUserSessionMaxAge = 60 * time.Minute
)

type Config struct {
Expand All @@ -36,12 +37,14 @@ type UserSessionData struct {
}

type CookieSettings struct {
Name string
Path string
MaxAge time.Duration
Secure bool
HttpOnly bool //nolint:stylecheck // naming from http.Cookie
SameSite http.SameSite
Path string
authSessionName string
userSessionName string
authSessionMaxAge time.Duration
userSessionMaxAge time.Duration
Secure bool
HttpOnly bool //nolint:stylecheck // naming from http.Cookie
SameSite http.SameSite
}

type Passkey struct {
Expand Down Expand Up @@ -71,15 +74,9 @@ func New(cfg Config, opts ...Option) (*Passkey, error) {

mux: http.NewServeMux(),
staticMux: http.NewServeMux(),

cookieSettings: CookieSettings{
Path: "/",
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
},
}

p.setupCookieSettings()
p.setupOptions(opts)
p.setupRoutes()

Expand Down Expand Up @@ -124,8 +121,8 @@ func (p *Passkey) setupOptions(opts []Option) {
func setupDefaultOptions(p *Passkey) {
defaultOpts := []Option{
WithLogger(logger.NewLogger()),
WithSessionCookieName(defaultSessionCookieName),
WithCookieMaxAge(defaultCookieMaxAge),
WithSessionCookieNamePrefix(defaultSessionNamePrefix),
WithUserSessionMaxAge(defaultUserSessionMaxAge),
}

for _, opt := range defaultOpts {
Expand Down Expand Up @@ -170,12 +167,15 @@ func (p *Passkey) MountRoutes(mux *http.ServeMux, path string) {
mux.Handle(path, http.StripPrefix(path[:len(path)-1], p.mux))
}

func defaultSessionIDGenerator() (string, error) {
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
return "", err
func (p *Passkey) setupCookieSettings() {
p.cookieSettings = CookieSettings{
Path: "/",
authSessionName: defaultAuthSessionName,
userSessionName: defaultUserSessionName,
authSessionMaxAge: defaultAuthSessionMaxAge,
userSessionMaxAge: defaultUserSessionMaxAge,
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
}

return base64.URLEncoding.EncodeToString(b), nil
}
15 changes: 12 additions & 3 deletions utils.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package passkey

import "net/http"
import (
"net/http"
"time"
)

// Logout deletes session from session store and deletes session cookie
// TODO: put it somewhere else
func (p *Passkey) Logout(w http.ResponseWriter, r *http.Request) {
sid, err := r.Cookie(p.cookieSettings.Name)
sid, err := r.Cookie(p.cookieSettings.userSessionName)
if err != nil {
p.log.Errorf("can't get session cookie: %s", err.Error())

return
}

p.authSessionStore.Delete(sid.Value)
p.deleteSessionCookie(w)
http.SetCookie(w, &http.Cookie{
Name: p.cookieSettings.userSessionName,
Value: "",
Expires: time.Unix(0, 0),
MaxAge: -1,
})
}
2 changes: 1 addition & 1 deletion utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestPasskey_Logout(t *testing.T) {
r: func() *http.Request {
r := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
r.AddCookie(&http.Cookie{
Name: defaultSessionCookieName,
Name: defaultAuthSessionName,
Value: "hello-darkness-my-old-friend",
})

Expand Down

0 comments on commit f5fc2f1

Please sign in to comment.