-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
db63622
commit 0953482
Showing
16 changed files
with
489 additions
and
411 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package auth | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/adam-hanna/sessions/sessionerrs" | ||
) | ||
|
||
// Service performs signing and verification actions using HMAC | ||
type Service struct { | ||
options Options | ||
} | ||
|
||
// Options defines the behavior of the auth service | ||
type Options struct { | ||
// Key is a slice of bytes for performing HMAC signing and verification operations | ||
Key []byte | ||
} | ||
|
||
// New returns a new auth service | ||
func New(options Options) (*Service, *sessionerrs.Custom) { | ||
// note @adam-hanna: should we perform other checks like min/max length? | ||
if len(options.Key) == 0 { | ||
return &Service{}, &sessionerrs.Custom{ | ||
Code: 500, | ||
Err: errors.New("no session key"), | ||
} | ||
} | ||
return &Service{ | ||
options: options, | ||
}, nil | ||
} | ||
|
||
// SignAndBase64Encode signs the sessionID with they key and returns a base64 encoded string | ||
func (s *Service) SignAndBase64Encode(sessionID string) (string, *sessionerrs.Custom) { | ||
userSessionIDBytes := []byte(sessionID) | ||
signedBytes := signHMAC(&userSessionIDBytes, &s.options.Key) | ||
|
||
// append the signature to the session id | ||
sessionValBytes := make([]byte, len(userSessionIDBytes)+len(signedBytes)) | ||
sessionValBytes = append(userSessionIDBytes, signedBytes...) | ||
|
||
return string(encode(sessionValBytes)[:]), nil | ||
} | ||
|
||
// VerifyAndDecode takes in a signed session string and returns a sessionID, only if the signed string passes | ||
// auth verification. | ||
func (s *Service) VerifyAndDecode(signed string) (string, *sessionerrs.Custom) { | ||
sessionValueBytes := []byte(signed) | ||
decodedSessionValueBytes, err := decode(sessionValueBytes) | ||
if err != nil { | ||
return "", &sessionerrs.Custom{ | ||
Code: 500, | ||
Err: err, | ||
} | ||
} | ||
|
||
// note: session uuid's are always 36 bytes long | ||
sessionIDBytes := decodedSessionValueBytes[:36] | ||
hmacBytes := decodedSessionValueBytes[36:] | ||
// fmt.Printf("In auth.VerifyAndDecode\nsessionID: %s\nsig: %x\nkey: %s\n", string(sessionIDBytes[:]), string(hmacBytes[:]), string(s.options.Key[:])) | ||
|
||
// verify the hmac signature | ||
verified := verifyHMAC(&sessionIDBytes, &hmacBytes, &s.options.Key) | ||
if !verified { | ||
return "", &sessionerrs.Custom{ | ||
Code: 401, | ||
Err: errors.New("invalid session signature"), | ||
} | ||
} | ||
|
||
return string(sessionIDBytes[:]), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package auth | ||
|
||
import "github.com/adam-hanna/sessions/sessionerrs" | ||
|
||
// ServiceInterface defines the methods that are performend by the auth service | ||
type ServiceInterface interface { | ||
SignAndBase64Encode(sessionID string) (string, *sessionerrs.Custom) | ||
VerifyAndDecode(signed string) (string, *sessionerrs.Custom) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package sessions | ||
|
||
import ( | ||
"net/http" | ||
"time" | ||
|
||
"github.com/adam-hanna/sessions/auth" | ||
"github.com/adam-hanna/sessions/sessionerrs" | ||
"github.com/adam-hanna/sessions/store" | ||
"github.com/adam-hanna/sessions/transport" | ||
"github.com/adam-hanna/sessions/user" | ||
) | ||
|
||
const ( | ||
// DefaultExpirationDuration sets the default session expiration duration | ||
DefaultExpirationDuration = 3 * 24 * time.Hour // 3 days | ||
) | ||
|
||
// Service provides session service for http servers | ||
type Service struct { | ||
store store.ServiceInterface | ||
auth auth.ServiceInterface | ||
transport transport.ServiceInterface | ||
options Options | ||
} | ||
|
||
// Options defines the behavior of the session service | ||
type Options struct { | ||
ExpirationDuration time.Duration | ||
} | ||
|
||
// New returns a new session service | ||
func New(store store.ServiceInterface, auth auth.ServiceInterface, transport transport.ServiceInterface, options Options) *Service { | ||
setDefaultOptions(&options) | ||
return &Service{ | ||
store: store, | ||
auth: auth, | ||
transport: transport, | ||
options: options, | ||
} | ||
} | ||
|
||
// IssueUserSession grants a new user session, writes that session info to the store \ | ||
// and writes the session on the http.ResponseWriter. | ||
// | ||
// This method should be called when a user logs in, for example. | ||
func (s *Service) IssueUserSession(userID string, json string, w http.ResponseWriter) (*user.Session, *sessionerrs.Custom) { | ||
userSession := user.New(userID, json, s.options.ExpirationDuration) | ||
|
||
// sign the session id | ||
signedSessionID, err := s.auth.SignAndBase64Encode(userSession.ID) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// save the session in the store | ||
err = s.store.SaveUserSession(userSession) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// set the session on the responseWriter | ||
return userSession, s.transport.SetSessionOnResponse(signedSessionID, userSession, w) | ||
} | ||
|
||
// ClearUserSession is used to remove the user session from the store and clear the cookies on the ResponseWriter. | ||
// | ||
// This method should be called when a user logs out, for example. | ||
func (s *Service) ClearUserSession(userSession *user.Session, w http.ResponseWriter) *sessionerrs.Custom { | ||
// delete the session from the store | ||
err := s.store.DeleteUserSession(userSession.ID) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// delete the session from the response | ||
return s.transport.DeleteSessionFromResponse(w) | ||
} | ||
|
||
// GetUserSession returns a user session from a request. This method only returns valid sessions. Therefore, \ | ||
// sessions that have expired, or that fail signature verification will return a custom session error with code 401 | ||
func (s *Service) GetUserSession(r *http.Request) (*user.Session, *sessionerrs.Custom) { | ||
// read the session from the request | ||
signedSessionID, err := s.transport.FetchSessionFromRequest(r) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// decode the signedSessionID | ||
sessionID, err := s.auth.VerifyAndDecode(signedSessionID) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// try fetching a valid session from the store | ||
return s.store.FetchValidUserSession(sessionID) | ||
} | ||
|
||
// ExtendUserSession extends the ExpiresAt of a session by the Options.ExpirationDuration | ||
func (s *Service) ExtendUserSession(userSession *user.Session, r *http.Request, w http.ResponseWriter) *sessionerrs.Custom { | ||
newExpiresAt := time.Now().Add(s.options.ExpirationDuration).UTC() | ||
|
||
// update the provided user session | ||
userSession.ExpiresAt = newExpiresAt | ||
|
||
// save the session in the store with the extended expiry | ||
err := s.store.SaveUserSession(userSession) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// fetch the signed session id from the request | ||
signedSessionID, err := s.transport.FetchSessionFromRequest(r) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// finally, set the session on the responseWriter | ||
return s.transport.SetSessionOnResponse(signedSessionID, userSession, w) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package sessions | ||
|
||
// setDefaultOptions sets default values for nil fields | ||
// note @adam-hanna: this utility function should be improved. The fields and types of the options struct \ | ||
// should not be hardcoded! | ||
func setDefaultOptions(options *Options) { | ||
emptyOptions := Options{} | ||
if options.ExpirationDuration == emptyOptions.ExpirationDuration { | ||
options.ExpirationDuration = DefaultExpirationDuration | ||
} | ||
|
||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.