2022-01-14 23:03:31 +08:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2022-01-14 23:03:31 +08:00
package auth
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
2022-10-18 06:50:37 +01:00
"code.gitea.io/gitea/modules/util"
2022-01-14 23:03:31 +08:00
2024-11-27 00:04:17 +08:00
"github.com/go-webauthn/webauthn/protocol"
2023-01-11 21:51:00 -05:00
"github.com/go-webauthn/webauthn/webauthn"
2022-01-14 23:03:31 +08:00
)
// ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error.
type ErrWebAuthnCredentialNotExist struct {
ID int64
2022-07-30 14:25:26 +01:00
CredentialID [ ] byte
2022-01-14 23:03:31 +08:00
}
func ( err ErrWebAuthnCredentialNotExist ) Error ( ) string {
2022-07-30 14:25:26 +01:00
if len ( err . CredentialID ) == 0 {
2022-01-14 23:03:31 +08:00
return fmt . Sprintf ( "WebAuthn credential does not exist [id: %d]" , err . ID )
}
2022-07-30 14:25:26 +01:00
return fmt . Sprintf ( "WebAuthn credential does not exist [credential_id: %x]" , err . CredentialID )
2022-01-14 23:03:31 +08:00
}
2022-10-18 06:50:37 +01:00
// Unwrap unwraps this as a ErrNotExist err
func ( err ErrWebAuthnCredentialNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2022-01-20 18:46:10 +01:00
// IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist.
2022-01-14 23:03:31 +08:00
func IsErrWebAuthnCredentialNotExist ( err error ) bool {
_ , ok := err . ( ErrWebAuthnCredentialNotExist )
return ok
}
2022-01-20 18:46:10 +01:00
// WebAuthnCredential represents the WebAuthn credential data for a public-key
// credential conformant to WebAuthn Level 1
2022-01-14 23:03:31 +08:00
type WebAuthnCredential struct {
ID int64 ` xorm:"pk autoincr" `
Name string
LowerName string ` xorm:"unique(s)" `
UserID int64 ` xorm:"INDEX unique(s)" `
2022-07-30 14:25:26 +01:00
CredentialID [ ] byte ` xorm:"INDEX VARBINARY(1024)" `
2022-01-14 23:03:31 +08:00
PublicKey [ ] byte
AttestationType string
AAGUID [ ] byte
SignCount uint32 ` xorm:"BIGINT" `
CloneWarning bool
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
}
func init ( ) {
db . RegisterModel ( new ( WebAuthnCredential ) )
}
// TableName returns a better table name for WebAuthnCredential
func ( cred WebAuthnCredential ) TableName ( ) string {
return "webauthn_credential"
}
// UpdateSignCount will update the database value of SignCount
2023-09-16 16:39:12 +02:00
func ( cred * WebAuthnCredential ) UpdateSignCount ( ctx context . Context ) error {
2022-01-14 23:03:31 +08:00
_ , err := db . GetEngine ( ctx ) . ID ( cred . ID ) . Cols ( "sign_count" ) . Update ( cred )
return err
}
// BeforeInsert will be invoked by XORM before updating a record
func ( cred * WebAuthnCredential ) BeforeInsert ( ) {
cred . LowerName = strings . ToLower ( cred . Name )
}
// BeforeUpdate will be invoked by XORM before updating a record
func ( cred * WebAuthnCredential ) BeforeUpdate ( ) {
cred . LowerName = strings . ToLower ( cred . Name )
}
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
2024-01-15 10:19:25 +08:00
func ( cred * WebAuthnCredential ) AfterLoad ( ) {
2022-01-14 23:03:31 +08:00
cred . LowerName = strings . ToLower ( cred . Name )
}
// WebAuthnCredentialList is a list of *WebAuthnCredential
type WebAuthnCredentialList [ ] * WebAuthnCredential
2024-11-27 00:04:17 +08:00
// newCredentialFlagsFromAuthenticatorFlags is copied from https://github.com/go-webauthn/webauthn/pull/337
// to convert protocol.AuthenticatorFlags to webauthn.CredentialFlags
func newCredentialFlagsFromAuthenticatorFlags ( flags protocol . AuthenticatorFlags ) webauthn . CredentialFlags {
return webauthn . CredentialFlags {
UserPresent : flags . HasUserPresent ( ) ,
UserVerified : flags . HasUserVerified ( ) ,
BackupEligible : flags . HasBackupEligible ( ) ,
BackupState : flags . HasBackupState ( ) ,
}
}
2022-01-14 23:03:31 +08:00
// ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials
2024-11-27 00:04:17 +08:00
func ( list WebAuthnCredentialList ) ToCredentials ( defaultAuthFlags ... protocol . AuthenticatorFlags ) [ ] webauthn . Credential {
// TODO: at the moment, Gitea doesn't store or check the flags
// so we need to use the default flags from the authenticator to make the login validation pass
// In the future, we should:
// 1. store the flags when registering the credential
// 2. provide the stored flags when converting the credentials (for login)
// 3. for old users, still use this fallback to the default flags
defAuthFlags := util . OptionalArg ( defaultAuthFlags )
2022-01-14 23:03:31 +08:00
creds := make ( [ ] webauthn . Credential , 0 , len ( list ) )
for _ , cred := range list {
creds = append ( creds , webauthn . Credential {
2022-07-30 14:25:26 +01:00
ID : cred . CredentialID ,
2022-01-14 23:03:31 +08:00
PublicKey : cred . PublicKey ,
AttestationType : cred . AttestationType ,
2024-11-27 00:04:17 +08:00
Flags : newCredentialFlagsFromAuthenticatorFlags ( defAuthFlags ) ,
2022-01-14 23:03:31 +08:00
Authenticator : webauthn . Authenticator {
AAGUID : cred . AAGUID ,
SignCount : cred . SignCount ,
CloneWarning : cred . CloneWarning ,
} ,
} )
}
return creds
}
2022-01-20 18:46:10 +01:00
// GetWebAuthnCredentialsByUID returns all WebAuthn credentials of the given user
2023-09-16 16:39:12 +02:00
func GetWebAuthnCredentialsByUID ( ctx context . Context , uid int64 ) ( WebAuthnCredentialList , error ) {
2022-01-14 23:03:31 +08:00
creds := make ( WebAuthnCredentialList , 0 )
return creds , db . GetEngine ( ctx ) . Where ( "user_id = ?" , uid ) . Find ( & creds )
}
2022-01-20 18:46:10 +01:00
// ExistsWebAuthnCredentialsForUID returns if the given user has credentials
2023-09-16 16:39:12 +02:00
func ExistsWebAuthnCredentialsForUID ( ctx context . Context , uid int64 ) ( bool , error ) {
2022-01-14 23:03:31 +08:00
return db . GetEngine ( ctx ) . Where ( "user_id = ?" , uid ) . Exist ( & WebAuthnCredential { } )
}
// GetWebAuthnCredentialByName returns WebAuthn credential by id
2023-09-16 16:39:12 +02:00
func GetWebAuthnCredentialByName ( ctx context . Context , uid int64 , name string ) ( * WebAuthnCredential , error ) {
2022-01-14 23:03:31 +08:00
cred := new ( WebAuthnCredential )
if found , err := db . GetEngine ( ctx ) . Where ( "user_id = ? AND lower_name = ?" , uid , strings . ToLower ( name ) ) . Get ( cred ) ; err != nil {
return nil , err
} else if ! found {
return nil , ErrWebAuthnCredentialNotExist { }
}
return cred , nil
}
// GetWebAuthnCredentialByID returns WebAuthn credential by id
2023-09-16 16:39:12 +02:00
func GetWebAuthnCredentialByID ( ctx context . Context , id int64 ) ( * WebAuthnCredential , error ) {
2022-01-14 23:03:31 +08:00
cred := new ( WebAuthnCredential )
if found , err := db . GetEngine ( ctx ) . ID ( id ) . Get ( cred ) ; err != nil {
return nil , err
} else if ! found {
return nil , ErrWebAuthnCredentialNotExist { ID : id }
}
return cred , nil
}
// HasWebAuthnRegistrationsByUID returns whether a given user has WebAuthn registrations
2023-09-16 16:39:12 +02:00
func HasWebAuthnRegistrationsByUID ( ctx context . Context , uid int64 ) ( bool , error ) {
return db . GetEngine ( ctx ) . Where ( "user_id = ?" , uid ) . Exist ( & WebAuthnCredential { } )
2022-01-14 23:03:31 +08:00
}
// GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
2023-09-16 16:39:12 +02:00
func GetWebAuthnCredentialByCredID ( ctx context . Context , userID int64 , credID [ ] byte ) ( * WebAuthnCredential , error ) {
2022-01-14 23:03:31 +08:00
cred := new ( WebAuthnCredential )
2022-01-15 16:52:56 +00:00
if found , err := db . GetEngine ( ctx ) . Where ( "user_id = ? AND credential_id = ?" , userID , credID ) . Get ( cred ) ; err != nil {
2022-01-14 23:03:31 +08:00
return nil , err
} else if ! found {
return nil , ErrWebAuthnCredentialNotExist { CredentialID : credID }
}
return cred , nil
}
// CreateCredential will create a new WebAuthnCredential from the given Credential
2023-09-16 16:39:12 +02:00
func CreateCredential ( ctx context . Context , userID int64 , name string , cred * webauthn . Credential ) ( * WebAuthnCredential , error ) {
2022-01-14 23:03:31 +08:00
c := & WebAuthnCredential {
UserID : userID ,
Name : name ,
2022-07-30 14:25:26 +01:00
CredentialID : cred . ID ,
2022-01-14 23:03:31 +08:00
PublicKey : cred . PublicKey ,
AttestationType : cred . AttestationType ,
AAGUID : cred . Authenticator . AAGUID ,
SignCount : cred . Authenticator . SignCount ,
CloneWarning : false ,
}
if err := db . Insert ( ctx , c ) ; err != nil {
return nil , err
}
return c , nil
}
// DeleteCredential will delete WebAuthnCredential
2023-09-16 16:39:12 +02:00
func DeleteCredential ( ctx context . Context , id , userID int64 ) ( bool , error ) {
2022-01-14 23:03:31 +08:00
had , err := db . GetEngine ( ctx ) . ID ( id ) . Where ( "user_id = ?" , userID ) . Delete ( & WebAuthnCredential { } )
return had > 0 , err
}
2024-06-30 00:50:03 +02:00
// WebAuthnCredentials implements the webauthn.User interface
2023-09-16 16:39:12 +02:00
func WebAuthnCredentials ( ctx context . Context , userID int64 ) ( [ ] webauthn . Credential , error ) {
dbCreds , err := GetWebAuthnCredentialsByUID ( ctx , userID )
2022-01-14 23:03:31 +08:00
if err != nil {
return nil , err
}
return dbCreds . ToCredentials ( ) , nil
}