mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Quick fix #32595, use authenticator auth flags to login --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
		
			
				
	
	
		
			213 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			213 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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
 | 
						|
}
 |