code.gitea.io/gitea@v1.22.3/models/auth/webauthn.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package auth 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 11 "code.gitea.io/gitea/models/db" 12 "code.gitea.io/gitea/modules/timeutil" 13 "code.gitea.io/gitea/modules/util" 14 15 "github.com/go-webauthn/webauthn/webauthn" 16 ) 17 18 // ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error. 19 type ErrWebAuthnCredentialNotExist struct { 20 ID int64 21 CredentialID []byte 22 } 23 24 func (err ErrWebAuthnCredentialNotExist) Error() string { 25 if len(err.CredentialID) == 0 { 26 return fmt.Sprintf("WebAuthn credential does not exist [id: %d]", err.ID) 27 } 28 return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID) 29 } 30 31 // Unwrap unwraps this as a ErrNotExist err 32 func (err ErrWebAuthnCredentialNotExist) Unwrap() error { 33 return util.ErrNotExist 34 } 35 36 // IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist. 37 func IsErrWebAuthnCredentialNotExist(err error) bool { 38 _, ok := err.(ErrWebAuthnCredentialNotExist) 39 return ok 40 } 41 42 // WebAuthnCredential represents the WebAuthn credential data for a public-key 43 // credential conformant to WebAuthn Level 1 44 type WebAuthnCredential struct { 45 ID int64 `xorm:"pk autoincr"` 46 Name string 47 LowerName string `xorm:"unique(s)"` 48 UserID int64 `xorm:"INDEX unique(s)"` 49 CredentialID []byte `xorm:"INDEX VARBINARY(1024)"` 50 PublicKey []byte 51 AttestationType string 52 AAGUID []byte 53 SignCount uint32 `xorm:"BIGINT"` 54 CloneWarning bool 55 CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` 56 UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` 57 } 58 59 func init() { 60 db.RegisterModel(new(WebAuthnCredential)) 61 } 62 63 // TableName returns a better table name for WebAuthnCredential 64 func (cred WebAuthnCredential) TableName() string { 65 return "webauthn_credential" 66 } 67 68 // UpdateSignCount will update the database value of SignCount 69 func (cred *WebAuthnCredential) UpdateSignCount(ctx context.Context) error { 70 _, err := db.GetEngine(ctx).ID(cred.ID).Cols("sign_count").Update(cred) 71 return err 72 } 73 74 // BeforeInsert will be invoked by XORM before updating a record 75 func (cred *WebAuthnCredential) BeforeInsert() { 76 cred.LowerName = strings.ToLower(cred.Name) 77 } 78 79 // BeforeUpdate will be invoked by XORM before updating a record 80 func (cred *WebAuthnCredential) BeforeUpdate() { 81 cred.LowerName = strings.ToLower(cred.Name) 82 } 83 84 // AfterLoad is invoked from XORM after setting the values of all fields of this object. 85 func (cred *WebAuthnCredential) AfterLoad() { 86 cred.LowerName = strings.ToLower(cred.Name) 87 } 88 89 // WebAuthnCredentialList is a list of *WebAuthnCredential 90 type WebAuthnCredentialList []*WebAuthnCredential 91 92 // ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials 93 func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential { 94 creds := make([]webauthn.Credential, 0, len(list)) 95 for _, cred := range list { 96 creds = append(creds, webauthn.Credential{ 97 ID: cred.CredentialID, 98 PublicKey: cred.PublicKey, 99 AttestationType: cred.AttestationType, 100 Authenticator: webauthn.Authenticator{ 101 AAGUID: cred.AAGUID, 102 SignCount: cred.SignCount, 103 CloneWarning: cred.CloneWarning, 104 }, 105 }) 106 } 107 return creds 108 } 109 110 // GetWebAuthnCredentialsByUID returns all WebAuthn credentials of the given user 111 func GetWebAuthnCredentialsByUID(ctx context.Context, uid int64) (WebAuthnCredentialList, error) { 112 creds := make(WebAuthnCredentialList, 0) 113 return creds, db.GetEngine(ctx).Where("user_id = ?", uid).Find(&creds) 114 } 115 116 // ExistsWebAuthnCredentialsForUID returns if the given user has credentials 117 func ExistsWebAuthnCredentialsForUID(ctx context.Context, uid int64) (bool, error) { 118 return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{}) 119 } 120 121 // GetWebAuthnCredentialByName returns WebAuthn credential by id 122 func GetWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*WebAuthnCredential, error) { 123 cred := new(WebAuthnCredential) 124 if found, err := db.GetEngine(ctx).Where("user_id = ? AND lower_name = ?", uid, strings.ToLower(name)).Get(cred); err != nil { 125 return nil, err 126 } else if !found { 127 return nil, ErrWebAuthnCredentialNotExist{} 128 } 129 return cred, nil 130 } 131 132 // GetWebAuthnCredentialByID returns WebAuthn credential by id 133 func GetWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) { 134 cred := new(WebAuthnCredential) 135 if found, err := db.GetEngine(ctx).ID(id).Get(cred); err != nil { 136 return nil, err 137 } else if !found { 138 return nil, ErrWebAuthnCredentialNotExist{ID: id} 139 } 140 return cred, nil 141 } 142 143 // HasWebAuthnRegistrationsByUID returns whether a given user has WebAuthn registrations 144 func HasWebAuthnRegistrationsByUID(ctx context.Context, uid int64) (bool, error) { 145 return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{}) 146 } 147 148 // GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID 149 func GetWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) { 150 cred := new(WebAuthnCredential) 151 if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil { 152 return nil, err 153 } else if !found { 154 return nil, ErrWebAuthnCredentialNotExist{CredentialID: credID} 155 } 156 return cred, nil 157 } 158 159 // CreateCredential will create a new WebAuthnCredential from the given Credential 160 func CreateCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) { 161 c := &WebAuthnCredential{ 162 UserID: userID, 163 Name: name, 164 CredentialID: cred.ID, 165 PublicKey: cred.PublicKey, 166 AttestationType: cred.AttestationType, 167 AAGUID: cred.Authenticator.AAGUID, 168 SignCount: cred.Authenticator.SignCount, 169 CloneWarning: false, 170 } 171 172 if err := db.Insert(ctx, c); err != nil { 173 return nil, err 174 } 175 return c, nil 176 } 177 178 // DeleteCredential will delete WebAuthnCredential 179 func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) { 180 had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{}) 181 return had > 0, err 182 } 183 184 // WebAuthnCredentials implementns the webauthn.User interface 185 func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) { 186 dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID) 187 if err != nil { 188 return nil, err 189 } 190 191 return dbCreds.ToCredentials(), nil 192 }