@@ -18,12 +18,18 @@ import (
18
18
"github.com/go-webauthn/webauthn/webauthn"
19
19
)
20
20
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"
26
30
31
+ // DynamoUser holds user data from DynamoDB, in both encrypted and unencrypted form. It also holds a Webauthn client
32
+ // and Webauthn API data.
27
33
type DynamoUser struct {
28
34
// Shared fields between U2F and WebAuthn
29
35
ID string `dynamodbav:"uuid" json:"uuid"`
@@ -53,6 +59,7 @@ type DynamoUser struct {
53
59
Icon string `dynamodbav:"-" json:"-"`
54
60
}
55
61
62
+ // NewDynamoUser creates a new DynamoUser from API input data, a storage client and a Webauthn client.
56
63
func NewDynamoUser (apiConfig ApiMeta , storage * Storage , apiKey ApiKey , webAuthnClient * webauthn.WebAuthn ) DynamoUser {
57
64
u := DynamoUser {
58
65
ID : apiConfig .UserUUID ,
@@ -76,6 +83,8 @@ func NewDynamoUser(apiConfig ApiMeta, storage *Storage, apiKey ApiKey, webAuthnC
76
83
return u
77
84
}
78
85
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.
79
88
func (u * DynamoUser ) RemoveU2F () {
80
89
u .AppId = ""
81
90
u .EncryptedAppId = ""
@@ -85,11 +94,14 @@ func (u *DynamoUser) RemoveU2F() {
85
94
u .EncryptedPublicKey = ""
86
95
}
87
96
97
+ // unsetSessionData clears the encrypted session data from a user and stores the updated record in the database.
88
98
func (u * DynamoUser ) unsetSessionData () error {
89
99
u .EncryptedSessionData = nil
90
100
return u .Store .Store (envConfig .WebauthnTable , u )
91
101
}
92
102
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.
93
105
func (u * DynamoUser ) saveSessionData (sessionData webauthn.SessionData ) error {
94
106
// load to be sure working with latest data
95
107
err := u .Load ()
@@ -112,6 +124,9 @@ func (u *DynamoUser) saveSessionData(sessionData webauthn.SessionData) error {
112
124
return u .Store .Store (envConfig .WebauthnTable , u )
113
125
}
114
126
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.
115
130
func (u * DynamoUser ) saveNewCredential (credential webauthn.Credential ) error {
116
131
// load to be sure working with latest data
117
132
err := u .Load ()
@@ -135,8 +150,9 @@ func (u *DynamoUser) saveNewCredential(credential webauthn.Credential) error {
135
150
136
151
// DeleteCredential expects a hashed-encoded credential id. It finds a matching credential for that user and saves the
137
152
// 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
139
154
// blanked out.
155
+ // CAUTION: user data is refreshed from the database by this function. Any unsaved data will be lost.
140
156
func (u * DynamoUser ) DeleteCredential (credIDHash string ) (int , error ) {
141
157
// load to be sure working with the latest data
142
158
err := u .Load ()
@@ -180,6 +196,7 @@ func (u *DynamoUser) DeleteCredential(credIDHash string) (int, error) {
180
196
return http .StatusNoContent , nil
181
197
}
182
198
199
+ // encryptAndStoreCredentials encrypts the user's credential list and updates the database record
183
200
func (u * DynamoUser ) encryptAndStoreCredentials () error {
184
201
js , err := json .Marshal (u .Credentials )
185
202
if err != nil {
@@ -195,6 +212,7 @@ func (u *DynamoUser) encryptAndStoreCredentials() error {
195
212
return u .Store .Store (envConfig .WebauthnTable , u )
196
213
}
197
214
215
+ // Load refreshes a user object from the database record and decrypts the session data and credential list
198
216
func (u * DynamoUser ) Load () error {
199
217
err := u .Store .Load (envConfig .WebauthnTable , WebAuthnTablePK , u .ID , u )
200
218
if err != nil {
@@ -243,10 +261,13 @@ func (u *DynamoUser) Load() error {
243
261
return nil
244
262
}
245
263
264
+ // Delete removes the user from the database
246
265
func (u * DynamoUser ) Delete () error {
247
266
return u .Store .Delete (envConfig .WebauthnTable , WebAuthnTablePK , u .ID )
248
267
}
249
268
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.
250
271
func (u * DynamoUser ) BeginRegistration () (* protocol.CredentialCreation , error ) {
251
272
if u .WebAuthnClient == nil {
252
273
return nil , fmt .Errorf ("dynamoUser, %s, missing WebAuthClient in BeginRegistration" , u .Name )
@@ -271,6 +292,9 @@ func (u *DynamoUser) BeginRegistration() (*protocol.CredentialCreation, error) {
271
292
return options , nil
272
293
}
273
294
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.
274
298
func (u * DynamoUser ) FinishRegistration (r * http.Request ) (string , error ) {
275
299
if r .Body == nil {
276
300
return "" , fmt .Errorf ("request Body may not be nil in FinishRegistration" )
@@ -304,6 +328,8 @@ func (u *DynamoUser) FinishRegistration(r *http.Request) (string, error) {
304
328
return keyHandleHash , u .unsetSessionData ()
305
329
}
306
330
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.
307
333
func (u * DynamoUser ) BeginLogin () (* protocol.CredentialAssertion , error ) {
308
334
extensions := protocol.AuthenticationExtensions {}
309
335
if u .EncryptedAppId != "" {
@@ -328,6 +354,8 @@ func (u *DynamoUser) BeginLogin() (*protocol.CredentialAssertion, error) {
328
354
return options , nil
329
355
}
330
356
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.
331
359
func (u * DynamoUser ) FinishLogin (r * http.Request ) (* webauthn.Credential , error ) {
332
360
if r .Body == nil {
333
361
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)
363
391
}
364
392
365
393
// 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
367
395
// null byte sequence is []byte{158,233,101}
368
396
if isNullByteSlice (parsedResponse .Response .UserHandle ) {
369
397
parsedResponse .Response .UserHandle = nil
@@ -378,27 +406,27 @@ func (u *DynamoUser) FinishLogin(r *http.Request) (*webauthn.Credential, error)
378
406
return credential , nil
379
407
}
380
408
381
- // User ID according to the Relying Party
409
+ // WebAuthnID returns the user's ID according to the Relying Party
382
410
func (u * DynamoUser ) WebAuthnID () []byte {
383
411
return []byte (u .ID )
384
412
}
385
413
386
- // User Name according to the Relying Party
414
+ // WebAuthnName returns the user's name according to the Relying Party
387
415
func (u * DynamoUser ) WebAuthnName () string {
388
416
return u .Name
389
417
}
390
418
391
- // Display Name of the user
419
+ // WebAuthnDisplayName returns the display name of the user
392
420
func (u * DynamoUser ) WebAuthnDisplayName () string {
393
421
return u .DisplayName
394
422
}
395
423
396
- // User 's icon url
424
+ // WebAuthnIcon returns the user 's icon URL
397
425
func (u * DynamoUser ) WebAuthnIcon () string {
398
426
return u .Icon
399
427
}
400
428
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
402
430
func (u * DynamoUser ) WebAuthnCredentials () []webauthn.Credential {
403
431
creds := u .Credentials
404
432
@@ -466,7 +494,8 @@ func (u *DynamoUser) WebAuthnCredentials() []webauthn.Credential {
466
494
return creds
467
495
}
468
496
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)
470
499
func isNullByteSlice (slice []byte ) bool {
471
500
if len (slice ) != 3 {
472
501
return false
@@ -477,12 +506,15 @@ func isNullByteSlice(slice []byte) bool {
477
506
return false
478
507
}
479
508
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.
480
511
func hashAndEncodeKeyHandle (id []byte ) string {
481
512
hash := sha256 .Sum256 (id )
482
513
return base64 .RawURLEncoding .EncodeToString (hash [:])
483
514
}
484
515
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
486
518
func logProtocolError (msg string , err error ) {
487
519
var protocolError * protocol.Error
488
520
if errors .As (err , & protocolError ) {
0 commit comments