Skip to content

Commit 98caa89

Browse files
committed
add code docs for functions in webauthn.go and user.go
1 parent 638b4a5 commit 98caa89

File tree

2 files changed

+76
-15
lines changed

2 files changed

+76
-15
lines changed

user.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ import (
1818
"github.com/go-webauthn/webauthn/webauthn"
1919
)
2020

21-
const (
22-
UserContextKey = "user"
23-
WebAuthnTablePK = "uuid"
24-
LegacyU2FCredID = "u2f"
25-
)
21+
// UserContextKey is the context key that points to the authenticated user
22+
const UserContextKey = "user"
23+
24+
// WebAuthnTablePK is the primary key in the WebAuthn DynamoDB table
25+
const WebAuthnTablePK = "uuid"
26+
27+
// LegacyU2FCredID is a special case credential ID for legacy U2F support. At most one credential for each user may
28+
// have this in its ID field.
29+
const LegacyU2FCredID = "u2f"
2630

31+
// DynamoUser holds user data from DynamoDB, in both encrypted and unencrypted form. It also holds a Webauthn client
32+
// and Webauthn API data.
2733
type DynamoUser struct {
2834
// Shared fields between U2F and WebAuthn
2935
ID string `dynamodbav:"uuid" json:"uuid"`
@@ -53,6 +59,7 @@ type DynamoUser struct {
5359
Icon string `dynamodbav:"-" json:"-"`
5460
}
5561

62+
// NewDynamoUser creates a new DynamoUser from API input data, a storage client and a Webauthn client.
5663
func NewDynamoUser(apiConfig ApiMeta, storage *Storage, apiKey ApiKey, webAuthnClient *webauthn.WebAuthn) DynamoUser {
5764
u := DynamoUser{
5865
ID: apiConfig.UserUUID,
@@ -76,6 +83,8 @@ func NewDynamoUser(apiConfig ApiMeta, storage *Storage, apiKey ApiKey, webAuthnC
7683
return u
7784
}
7885

86+
// RemoveU2F clears U2F fields in the user struct. To be used when a user has requested removal of their legacy U2F key.
87+
// Should be followed by a database store operation.
7988
func (u *DynamoUser) RemoveU2F() {
8089
u.AppId = ""
8190
u.EncryptedAppId = ""
@@ -85,11 +94,14 @@ func (u *DynamoUser) RemoveU2F() {
8594
u.EncryptedPublicKey = ""
8695
}
8796

97+
// unsetSessionData clears the encrypted session data from a user and stores the updated record in the database.
8898
func (u *DynamoUser) unsetSessionData() error {
8999
u.EncryptedSessionData = nil
90100
return u.Store.Store(envConfig.WebauthnTable, u)
91101
}
92102

103+
// saveSessionData encrypts the user's session data and updates the database record.
104+
// CAUTION: user data is refreshed from the database by this function. Any unsaved data will be lost.
93105
func (u *DynamoUser) saveSessionData(sessionData webauthn.SessionData) error {
94106
// load to be sure working with latest data
95107
err := u.Load()
@@ -112,6 +124,9 @@ func (u *DynamoUser) saveSessionData(sessionData webauthn.SessionData) error {
112124
return u.Store.Store(envConfig.WebauthnTable, u)
113125
}
114126

127+
// saveNewCredential appends a new credential to the user's credential list, encrypts the list, and updates the
128+
// database record.
129+
// CAUTION: user data is refreshed from the database by this function. Any unsaved data will be lost.
115130
func (u *DynamoUser) saveNewCredential(credential webauthn.Credential) error {
116131
// load to be sure working with latest data
117132
err := u.Load()
@@ -135,8 +150,9 @@ func (u *DynamoUser) saveNewCredential(credential webauthn.Credential) error {
135150

136151
// DeleteCredential expects a hashed-encoded credential id. It finds a matching credential for that user and saves the
137152
// user without that credential included. Alternatively, if the given credential id indicates that a legacy U2F key
138-
// should be removed (e.g. by matching the string "u2f") then that user is saved with all of its legacy u2f fields
153+
// should be removed (i.e. by matching the string "u2f") then that user is saved with all of its legacy u2f fields
139154
// blanked out.
155+
// CAUTION: user data is refreshed from the database by this function. Any unsaved data will be lost.
140156
func (u *DynamoUser) DeleteCredential(credIDHash string) (int, error) {
141157
// load to be sure working with the latest data
142158
err := u.Load()
@@ -180,6 +196,7 @@ func (u *DynamoUser) DeleteCredential(credIDHash string) (int, error) {
180196
return http.StatusNoContent, nil
181197
}
182198

199+
// encryptAndStoreCredentials encrypts the user's credential list and updates the database record
183200
func (u *DynamoUser) encryptAndStoreCredentials() error {
184201
js, err := json.Marshal(u.Credentials)
185202
if err != nil {
@@ -195,6 +212,7 @@ func (u *DynamoUser) encryptAndStoreCredentials() error {
195212
return u.Store.Store(envConfig.WebauthnTable, u)
196213
}
197214

215+
// Load refreshes a user object from the database record and decrypts the session data and credential list
198216
func (u *DynamoUser) Load() error {
199217
err := u.Store.Load(envConfig.WebauthnTable, WebAuthnTablePK, u.ID, u)
200218
if err != nil {
@@ -243,10 +261,13 @@ func (u *DynamoUser) Load() error {
243261
return nil
244262
}
245263

264+
// Delete removes the user from the database
246265
func (u *DynamoUser) Delete() error {
247266
return u.Store.Delete(envConfig.WebauthnTable, WebAuthnTablePK, u.ID)
248267
}
249268

269+
// BeginRegistration processes the first half of the Webauthn Registration flow for the user and returns the
270+
// CredentialCreation data to pass back to the client. User session data is saved in the database.
250271
func (u *DynamoUser) BeginRegistration() (*protocol.CredentialCreation, error) {
251272
if u.WebAuthnClient == nil {
252273
return nil, fmt.Errorf("dynamoUser, %s, missing WebAuthClient in BeginRegistration", u.Name)
@@ -271,6 +292,9 @@ func (u *DynamoUser) BeginRegistration() (*protocol.CredentialCreation, error) {
271292
return options, nil
272293
}
273294

295+
// FinishRegistration processes the last half of the Webauthn Registration flow for the user and returns the
296+
// key_handle_hash to pass back to the client. The client should store this value for later use. User session data is
297+
// cleared from the database.
274298
func (u *DynamoUser) FinishRegistration(r *http.Request) (string, error) {
275299
if r.Body == nil {
276300
return "", fmt.Errorf("request Body may not be nil in FinishRegistration")
@@ -304,6 +328,8 @@ func (u *DynamoUser) FinishRegistration(r *http.Request) (string, error) {
304328
return keyHandleHash, u.unsetSessionData()
305329
}
306330

331+
// BeginLogin processes the first half of the Webauthn Authentication flow for the user and returns the
332+
// CredentialAssertion data to pass back to the client. User session data is saved in the database.
307333
func (u *DynamoUser) BeginLogin() (*protocol.CredentialAssertion, error) {
308334
extensions := protocol.AuthenticationExtensions{}
309335
if u.EncryptedAppId != "" {
@@ -328,6 +354,8 @@ func (u *DynamoUser) BeginLogin() (*protocol.CredentialAssertion, error) {
328354
return options, nil
329355
}
330356

357+
// FinishLogin processes the last half of the Webauthn Authentication flow for the user and returns the
358+
// Credential data to pass back to the client. User session data is untouched by this function.
331359
func (u *DynamoUser) FinishLogin(r *http.Request) (*webauthn.Credential, error) {
332360
if r.Body == nil {
333361
return nil, fmt.Errorf("request Body may not be nil in FinishLogin")
@@ -363,7 +391,7 @@ func (u *DynamoUser) FinishLogin(r *http.Request) (*webauthn.Credential, error)
363391
}
364392

365393
// there is an issue with URLEncodeBase64.UnmarshalJSON and null values
366-
// see https://github.com/go-webauthn/webauthn/issues/69
394+
// see https://github.com/duo-labs/webauthn/issues/69
367395
// null byte sequence is []byte{158,233,101}
368396
if isNullByteSlice(parsedResponse.Response.UserHandle) {
369397
parsedResponse.Response.UserHandle = nil
@@ -378,27 +406,27 @@ func (u *DynamoUser) FinishLogin(r *http.Request) (*webauthn.Credential, error)
378406
return credential, nil
379407
}
380408

381-
// User ID according to the Relying Party
409+
// WebAuthnID returns the user's ID according to the Relying Party
382410
func (u *DynamoUser) WebAuthnID() []byte {
383411
return []byte(u.ID)
384412
}
385413

386-
// User Name according to the Relying Party
414+
// WebAuthnName returns the user's name according to the Relying Party
387415
func (u *DynamoUser) WebAuthnName() string {
388416
return u.Name
389417
}
390418

391-
// Display Name of the user
419+
// WebAuthnDisplayName returns the display name of the user
392420
func (u *DynamoUser) WebAuthnDisplayName() string {
393421
return u.DisplayName
394422
}
395423

396-
// User's icon url
424+
// WebAuthnIcon returns the user's icon URL
397425
func (u *DynamoUser) WebAuthnIcon() string {
398426
return u.Icon
399427
}
400428

401-
// WebAuthnCredentials returns an array of credentials plus a U2F cred if present
429+
// WebAuthnCredentials returns an array of credentials (passkeys) plus a U2F credential if present
402430
func (u *DynamoUser) WebAuthnCredentials() []webauthn.Credential {
403431
creds := u.Credentials
404432

@@ -466,7 +494,8 @@ func (u *DynamoUser) WebAuthnCredentials() []webauthn.Credential {
466494
return creds
467495
}
468496

469-
// isNullByteSlice works around a bug in json unmarshalling for a urlencoded base64 string
497+
// isNullByteSlice works around a bug in JSON unmarshalling for a URL-encoded Base64 string
498+
// (see https://github.com/duo-labs/webauthn/issues/69)
470499
func isNullByteSlice(slice []byte) bool {
471500
if len(slice) != 3 {
472501
return false
@@ -477,12 +506,15 @@ func isNullByteSlice(slice []byte) bool {
477506
return false
478507
}
479508

509+
// hashAndEncodeKeyHandle returns the Base64 URL-encoded SHA256 hash of a byte slice to provide a hash of a key
510+
// handle to the client.
480511
func hashAndEncodeKeyHandle(id []byte) string {
481512
hash := sha256.Sum256(id)
482513
return base64.RawURLEncoding.EncodeToString(hash[:])
483514
}
484515

485-
// logProtocolError logs a detailed message if the given error is an Error from go-webauthn/webauthn/protocol
516+
// logProtocolError logs an error and includes additional detail if the given error is an Error from
517+
// go-webauthn/webauthn/protocol
486518
func logProtocolError(msg string, err error) {
487519
var protocolError *protocol.Error
488520
if errors.As(err, &protocolError) {

webauthn.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,19 @@ type beginRegistrationResponse struct {
3838
protocol.CredentialCreation
3939
}
4040

41+
// finishRegistrationResponse contains the response data for the FinishRegistration endpoint
4142
type finishRegistrationResponse struct {
4243
KeyHandleHash string `json:"key_handle_hash"`
4344
}
4445

46+
// finishLoginResponse contains the response data for the FinishLogin endpoint
4547
type finishLoginResponse struct {
4648
CredentialID string `json:"credentialId"` // DEPRECATED, use KeyHandleHash instead
4749
KeyHandleHash string `json:"key_handle_hash"`
4850
}
4951

52+
// BeginRegistration processes the first half of the Webauthn Registration flow. It is the handler for the
53+
// "POST /webauthn/register" endpoint, initiated by the client when creation of a new passkey is requested.
5054
func BeginRegistration(w http.ResponseWriter, r *http.Request) {
5155
user, err := getUserFromContext(r)
5256
if err != nil {
@@ -72,6 +76,8 @@ func BeginRegistration(w http.ResponseWriter, r *http.Request) {
7276
jsonResponse(w, response, http.StatusOK)
7377
}
7478

79+
// FinishRegistration processes the last half of the Webauthn Registration flow. It is the handler for the
80+
// "PUT /webauthn/register" endpoint, initiated by the client with information encrypted by the new private key.
7581
func FinishRegistration(w http.ResponseWriter, r *http.Request) {
7682
user, err := getUserFromContext(r)
7783
if err != nil {
@@ -92,6 +98,8 @@ func FinishRegistration(w http.ResponseWriter, r *http.Request) {
9298
jsonResponse(w, response, http.StatusOK) // Handle next steps
9399
}
94100

101+
// BeginLogin processes the first half of the Webauthn Authentication flow. It is the handler for the
102+
// "POST /webauthn/login" endpoint, initiated by the client at the beginning of a login request.
95103
func BeginLogin(w http.ResponseWriter, r *http.Request) {
96104
user, err := getUserFromContext(r)
97105
if err != nil {
@@ -110,6 +118,8 @@ func BeginLogin(w http.ResponseWriter, r *http.Request) {
110118
jsonResponse(w, options, http.StatusOK)
111119
}
112120

121+
// FinishLogin processes the second half of the Webauthn Authentication flow. It is the handler for the
122+
// "PUT /webauthn/login" endpoint, initiated by the client with login data signed with the private key.
113123
func FinishLogin(w http.ResponseWriter, r *http.Request) {
114124
user, err := getUserFromContext(r)
115125
if err != nil {
@@ -133,6 +143,8 @@ func FinishLogin(w http.ResponseWriter, r *http.Request) {
133143
jsonResponse(w, resp, http.StatusOK)
134144
}
135145

146+
// DeleteUser is the handler for the "DELETE /webauthn/user" endpoint. It removes a user and any stored passkeys owned
147+
// by the user.
136148
func DeleteUser(w http.ResponseWriter, r *http.Request) {
137149
user, err := getUserFromContext(r)
138150
if err != nil {
@@ -150,6 +162,9 @@ func DeleteUser(w http.ResponseWriter, r *http.Request) {
150162
jsonResponse(w, nil, http.StatusNoContent)
151163
}
152164

165+
// DeleteCredential is the handler for the "DELETE /webauthn/credential/{credID}" endpoint. It removes a single
166+
// passkey identified by "credID", which is the key_handle_hash returned by the FinishRegistration endpoint, or "u2f"
167+
// if it is a legacy U2F credential.
153168
func DeleteCredential(w http.ResponseWriter, r *http.Request) {
154169
user, err := getUserFromContext(r)
155170
if err != nil {
@@ -175,14 +190,18 @@ func DeleteCredential(w http.ResponseWriter, r *http.Request) {
175190
jsonResponse(w, err, status)
176191
}
177192

193+
// simpleError is a custom error type that can be JSON-encoded for API responses
178194
type simpleError struct {
179195
Error string `json:"error"`
180196
}
181197

198+
// newSimpleError creates a new simpleError from the given error
182199
func newSimpleError(err error) simpleError {
183200
return simpleError{Error: err.Error()}
184201
}
185202

203+
// jsonResponse encodes a body as JSON and writes it to the response. It sets the response Content-Type header to
204+
// "application/json".
186205
func jsonResponse(w http.ResponseWriter, body interface{}, status int) {
187206
var data interface{}
188207
switch b := body.(type) {
@@ -208,22 +227,26 @@ func jsonResponse(w http.ResponseWriter, body interface{}, status int) {
208227
w.WriteHeader(status)
209228
_, err = w.Write(jBody)
210229
if err != nil {
211-
log.Printf("faild to write response in jsonResponse: %s\n", err)
230+
log.Printf("failed to write response in jsonResponse: %s\n", err)
212231
}
213232
}
214233

234+
// fixStringEncoding converts a string from standard Base64 to Base64-URL
215235
func fixStringEncoding(content string) string {
216236
content = strings.ReplaceAll(content, "+", "-")
217237
content = strings.ReplaceAll(content, "/", "_")
218238
content = strings.ReplaceAll(content, "=", "")
219239
return content
220240
}
221241

242+
// fixEncoding converts a string from standard Base64 to Base64-URL as an io.Reader
222243
func fixEncoding(content []byte) io.Reader {
223244
allStr := string(content)
224245
return bytes.NewReader([]byte(fixStringEncoding(allStr)))
225246
}
226247

248+
// getWebAuthnFromApiMeta creates a new WebAuthn object from the metadata provided in a web request. Typically used in
249+
// the API authentication phase, early in the handler or in a middleware.
227250
func getWebAuthnFromApiMeta(meta ApiMeta) (*webauthn.WebAuthn, error) {
228251
web, err := webauthn.New(&webauthn.Config{
229252
RPDisplayName: meta.RPDisplayName, // Display Name for your site
@@ -238,6 +261,8 @@ func getWebAuthnFromApiMeta(meta ApiMeta) (*webauthn.WebAuthn, error) {
238261
return web, nil
239262
}
240263

264+
// getApiMetaFromRequest creates an ApiMeta object from request headers, including basic validation checks. Used during
265+
// API authentication.
241266
func getApiMetaFromRequest(r *http.Request) (ApiMeta, error) {
242267
meta := ApiMeta{
243268
RPDisplayName: r.Header.Get("x-mfa-RPDisplayName"),
@@ -271,6 +296,8 @@ func getApiMetaFromRequest(r *http.Request) (ApiMeta, error) {
271296
return meta, nil
272297
}
273298

299+
// getUserFromContext returns the authenticated DynamoUser from the request context. The authentication middleware or
300+
// early handler processing inserts the authenticated user into the context for retrieval by this function.
274301
func getUserFromContext(r *http.Request) (*DynamoUser, error) {
275302
user, ok := r.Context().Value(UserContextKey).(*DynamoUser)
276303
if !ok {
@@ -280,6 +307,8 @@ func getUserFromContext(r *http.Request) (*DynamoUser, error) {
280307
return user, nil
281308
}
282309

310+
// AuthenticateRequest checks the provided API key against the keys stored in the database. If the key is active and
311+
// valid, a Webauthn client and DynamoUser are created and stored in the request context.
283312
func AuthenticateRequest(r *http.Request) (*DynamoUser, error) {
284313
// get key and secret from headers
285314
key := r.Header.Get("x-mfa-apikey")

0 commit comments

Comments
 (0)