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  }