summaryrefslogtreecommitdiffstats
path: root/models/auth/webauthn.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
committerDaniel Baumann <daniel@debian.org>2024-10-18 20:33:49 +0200
commitdd136858f1ea40ad3c94191d647487fa4f31926c (patch)
tree58fec94a7b2a12510c9664b21793f1ed560c6518 /models/auth/webauthn.go
parentInitial commit. (diff)
downloadforgejo-debian.tar.xz
forgejo-debian.zip
Adding upstream version 9.0.0.upstream/9.0.0upstreamdebian
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'models/auth/webauthn.go')
-rw-r--r--models/auth/webauthn.go209
1 files changed, 209 insertions, 0 deletions
diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go
new file mode 100644
index 0000000..aa13cf6
--- /dev/null
+++ b/models/auth/webauthn.go
@@ -0,0 +1,209 @@
+// 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/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 3
+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
+ BackupEligible bool `XORM:"NOT NULL DEFAULT false"`
+ BackupState bool `XORM:"NOT NULL DEFAULT false"`
+ // If legacy is set to true, backup_eligible and backup_state isn't set.
+ Legacy bool `XORM:"NOT NULL DEFAULT true"`
+ 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
+}
+
+// UpdateFromLegacy update the values that aren't present on legacy credentials.
+func (cred *WebAuthnCredential) UpdateFromLegacy(ctx context.Context) error {
+ _, err := db.GetEngine(ctx).ID(cred.ID).Cols("legacy", "backup_eligible", "backup_state").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
+
+// ToCredentials will convert all WebAuthnCredentials to webauthn.Credentials
+func (list WebAuthnCredentialList) ToCredentials() []webauthn.Credential {
+ 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: webauthn.CredentialFlags{
+ BackupEligible: cred.BackupEligible,
+ BackupState: cred.BackupState,
+ },
+ 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,
+ BackupEligible: cred.Flags.BackupEligible,
+ BackupState: cred.Flags.BackupState,
+ Legacy: 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 implementns 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
+}