first-commit
This commit is contained in:
212
models/auth/webauthn.go
Normal file
212
models/auth/webauthn.go
Normal file
@@ -0,0 +1,212 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
)
|
||||
|
||||
// ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error.
|
||||
type ErrWebAuthnCredentialNotExist struct {
|
||||
ID int64
|
||||
CredentialID []byte
|
||||
}
|
||||
|
||||
func (err ErrWebAuthnCredentialNotExist) Error() string {
|
||||
if len(err.CredentialID) == 0 {
|
||||
return fmt.Sprintf("WebAuthn credential does not exist [id: %d]", err.ID)
|
||||
}
|
||||
return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %x]", err.CredentialID)
|
||||
}
|
||||
|
||||
// Unwrap unwraps this as a ErrNotExist err
|
||||
func (err ErrWebAuthnCredentialNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// IsErrWebAuthnCredentialNotExist checks if an error is a ErrWebAuthnCredentialNotExist.
|
||||
func IsErrWebAuthnCredentialNotExist(err error) bool {
|
||||
_, ok := err.(ErrWebAuthnCredentialNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
// WebAuthnCredential represents the WebAuthn credential data for a public-key
|
||||
// credential conformant to WebAuthn Level 1
|
||||
type WebAuthnCredential struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Name string
|
||||
LowerName string `xorm:"unique(s)"`
|
||||
UserID int64 `xorm:"INDEX unique(s)"`
|
||||
CredentialID []byte `xorm:"INDEX VARBINARY(1024)"`
|
||||
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
|
||||
func (cred *WebAuthnCredential) UpdateSignCount(ctx context.Context) error {
|
||||
_, 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.
|
||||
func (cred *WebAuthnCredential) AfterLoad() {
|
||||
cred.LowerName = strings.ToLower(cred.Name)
|
||||
}
|
||||
|
||||
// WebAuthnCredentialList is a list of *WebAuthnCredential
|
||||
type WebAuthnCredentialList []*WebAuthnCredential
|
||||
|
||||
// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
// ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials
|
||||
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)
|
||||
creds := make([]webauthn.Credential, 0, len(list))
|
||||
for _, cred := range list {
|
||||
creds = append(creds, webauthn.Credential{
|
||||
ID: cred.CredentialID,
|
||||
PublicKey: cred.PublicKey,
|
||||
AttestationType: cred.AttestationType,
|
||||
Flags: newCredentialFlagsFromAuthenticatorFlags(defAuthFlags),
|
||||
Authenticator: webauthn.Authenticator{
|
||||
AAGUID: cred.AAGUID,
|
||||
SignCount: cred.SignCount,
|
||||
CloneWarning: cred.CloneWarning,
|
||||
},
|
||||
})
|
||||
}
|
||||
return creds
|
||||
}
|
||||
|
||||
// GetWebAuthnCredentialsByUID returns all WebAuthn credentials of the given user
|
||||
func GetWebAuthnCredentialsByUID(ctx context.Context, uid int64) (WebAuthnCredentialList, error) {
|
||||
creds := make(WebAuthnCredentialList, 0)
|
||||
return creds, db.GetEngine(ctx).Where("user_id = ?", uid).Find(&creds)
|
||||
}
|
||||
|
||||
// ExistsWebAuthnCredentialsForUID returns if the given user has credentials
|
||||
func ExistsWebAuthnCredentialsForUID(ctx context.Context, uid int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
|
||||
}
|
||||
|
||||
// GetWebAuthnCredentialByName returns WebAuthn credential by id
|
||||
func GetWebAuthnCredentialByName(ctx context.Context, uid int64, name string) (*WebAuthnCredential, error) {
|
||||
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
|
||||
func GetWebAuthnCredentialByID(ctx context.Context, id int64) (*WebAuthnCredential, error) {
|
||||
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
|
||||
func HasWebAuthnRegistrationsByUID(ctx context.Context, uid int64) (bool, error) {
|
||||
return db.GetEngine(ctx).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
|
||||
}
|
||||
|
||||
// GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
|
||||
func GetWebAuthnCredentialByCredID(ctx context.Context, userID int64, credID []byte) (*WebAuthnCredential, error) {
|
||||
cred := new(WebAuthnCredential)
|
||||
if found, err := db.GetEngine(ctx).Where("user_id = ? AND credential_id = ?", userID, credID).Get(cred); err != nil {
|
||||
return nil, err
|
||||
} else if !found {
|
||||
return nil, ErrWebAuthnCredentialNotExist{CredentialID: credID}
|
||||
}
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
// CreateCredential will create a new WebAuthnCredential from the given Credential
|
||||
func CreateCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) {
|
||||
c := &WebAuthnCredential{
|
||||
UserID: userID,
|
||||
Name: name,
|
||||
CredentialID: cred.ID,
|
||||
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
|
||||
func DeleteCredential(ctx context.Context, id, userID int64) (bool, error) {
|
||||
had, err := db.GetEngine(ctx).ID(id).Where("user_id = ?", userID).Delete(&WebAuthnCredential{})
|
||||
return had > 0, err
|
||||
}
|
||||
|
||||
// WebAuthnCredentials implements the webauthn.User interface
|
||||
func WebAuthnCredentials(ctx context.Context, userID int64) ([]webauthn.Credential, error) {
|
||||
dbCreds, err := GetWebAuthnCredentialsByUID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dbCreds.ToCredentials(), nil
|
||||
}
|
Reference in New Issue
Block a user