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  }