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