diff --git a/app/application.go b/app/application.go index 1c670477..8e097652 100644 --- a/app/application.go +++ b/app/application.go @@ -283,6 +283,11 @@ func initRoutes(router *gin.Engine) { // UI Components webRoutes.GET("/ui_components", webui.ComponentsIndex) + // User MFA - Passkeys + webRoutes.POST("/users/begin_passkey_registration", webui.UserBeginPasskeyRegistration) + webRoutes.POST("/users/finish_passkey_registration", webui.UserFinishPasskeyRegistration) + webRoutes.POST("/users/begin_login_with_passkey", webui.UserBeginLoginWithPasskey) + webRoutes.POST("/users/finish_login_with_passkey", webui.UserFinishLoginWithPasskey) } // Root goes to sign-in page, which is a web route, diff --git a/common/context.go b/common/context.go index cee1c0c7..d9f1c582 100644 --- a/common/context.go +++ b/common/context.go @@ -8,6 +8,7 @@ import ( "github.com/APTrust/registry/constants" "github.com/APTrust/registry/network" "github.com/go-pg/pg/v10" + "github.com/go-webauthn/webauthn/webauthn" "github.com/rs/zerolog" ) @@ -46,6 +47,9 @@ type APTContext struct { // SMTPClient is for sending emails from a private subnet that // is not using a NAT gateway. SMTPClient *network.SMTPClient + + // WebAuthnClient is used for passkey authentication + WebAuthn *webauthn.WebAuthn } // Context returns an APTContext object, which includes @@ -90,6 +94,7 @@ func Context() *APTContext { SESClient: network.NewSESClient(config.Email.Enabled, config.TwoFactor.AWSRegion, config.Email.SesEndpoint, config.Email.SesUser, config.Email.SesPassword, config.Email.FromAddress, zlogger), SNSClient: network.NewSNSClient(config.TwoFactor.SMSEnabled, config.TwoFactor.AWSRegion, config.TwoFactor.SNSEndpoint, config.TwoFactor.SNSUser, config.TwoFactor.SNSPassword, zlogger), SMTPClient: network.NewSMTPClient(config.Email.Enabled, config.TwoFactor.AWSRegion, config.Email.SesEndpoint, config.Email.SesUser, config.Email.SesPassword, config.Email.FromAddress, zlogger), + WebAuthn: network.NewWebAuthn(), RedisClient: redisClient, } } diff --git a/constants/constants.go b/constants/constants.go index 3f31cfb3..f34fa70e 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -86,6 +86,7 @@ const ( RoleSysAdmin = "admin" SecondFactorAuthy = "Authy" SecondFactorBackupCode = "Backup Code" + SecondFactorPasskey = "Passkey" SecondFactorSMS = "SMS" StageAvailableInS3 = "Available in S3" StageCleanup = "Cleanup" @@ -134,6 +135,7 @@ const ( TopicObjectRestore = "restore_object" TwoFactorAuthy = "onetouch" TwoFactorNone = "none" + TwoFactorPasskey = "passkey" TwoFactorSMS = "sms" ) diff --git a/constants/permissions.go b/constants/permissions.go index ac97f99c..2c44cf1f 100644 --- a/constants/permissions.go +++ b/constants/permissions.go @@ -67,11 +67,15 @@ const ( StorageRecordDelete = "StorageRecordDelete" StorageRecordRead = "StorageRecordRead" StorageRecordUpdate = "StorageRecordUpdate" + UserBeginLoginWithPasskey = "UserBeginLoginWithPasskey" + UserBeginPasskeyRegistration = "UserBeginPasskeyRegistration" UserComplete2FASetup = "UserComplete2FASetup" UserConfirmPhone = "UserConfirmPhone" UserCreate = "UserCreate" UserDelete = "UserDelete" UserDeleteSelf = "UserDeleteSelf" + UserFinishLoginWithPasskey = "UserFinishLoginWithPasskey" + UserFinishPasskeyRegistration = "UserFinishPasskeyRegistration" UserGenerateBackupCodes = "UserGenerateBackupCodes" UserInit2FASetup = "UserInit2FASetup" UserRead = "UserRead" @@ -146,11 +150,15 @@ var Permissions = []Permission{ StorageRecordDelete, StorageRecordRead, StorageRecordUpdate, + UserBeginLoginWithPasskey, + UserBeginPasskeyRegistration, UserComplete2FASetup, UserConfirmPhone, UserCreate, UserDelete, UserDeleteSelf, + UserFinishLoginWithPasskey, + UserFinishPasskeyRegistration, UserGenerateBackupCodes, UserInit2FASetup, UserRead, @@ -225,8 +233,12 @@ func initPermissions() { instUser[IntellectualObjectRestore] = true instUser[ReportRead] = true instUser[StorageRecordRead] = true + instUser[UserBeginLoginWithPasskey] = true + instUser[UserBeginPasskeyRegistration] = true instUser[UserComplete2FASetup] = true instUser[UserConfirmPhone] = true + instUser[UserFinishLoginWithPasskey] = true + instUser[UserFinishPasskeyRegistration] = true instUser[UserGenerateBackupCodes] = true instUser[UserInit2FASetup] = true instUser[UserReadSelf] = true @@ -263,10 +275,14 @@ func initPermissions() { instAdmin[IntellectualObjectRestore] = true instAdmin[ReportRead] = true instAdmin[StorageRecordRead] = true + instAdmin[UserBeginLoginWithPasskey] = true + instAdmin[UserBeginPasskeyRegistration] = true instAdmin[UserComplete2FASetup] = true instAdmin[UserConfirmPhone] = true instAdmin[UserCreate] = true instAdmin[UserDelete] = true + instAdmin[UserFinishLoginWithPasskey] = true + instAdmin[UserFinishPasskeyRegistration] = true instAdmin[UserGenerateBackupCodes] = true instAdmin[UserInit2FASetup] = true instAdmin[UserReadSelf] = true @@ -335,11 +351,15 @@ func initPermissions() { sysAdmin[StorageRecordDelete] = true sysAdmin[StorageRecordRead] = true sysAdmin[StorageRecordUpdate] = true + sysAdmin[UserBeginLoginWithPasskey] = true + sysAdmin[UserBeginPasskeyRegistration] = true sysAdmin[UserComplete2FASetup] = true sysAdmin[UserConfirmPhone] = true sysAdmin[UserCreate] = true sysAdmin[UserDeleteSelf] = true sysAdmin[UserDelete] = true + sysAdmin[UserFinishLoginWithPasskey] = true + sysAdmin[UserFinishPasskeyRegistration] = true sysAdmin[UserGenerateBackupCodes] = true sysAdmin[UserInit2FASetup] = true sysAdmin[UserReadSelf] = true diff --git a/db/fixtures/users.csv b/db/fixtures/users.csv index 78247fdb..dde214a8 100644 --- a/db/fixtures/users.csv +++ b/db/fixtures/users.csv @@ -1,10 +1,10 @@ -id,name,email,phone_number,created_at,updated_at,encrypted_password,reset_password_token,reset_password_sent_at,remember_created_at,sign_in_count,current_sign_in_at,last_sign_in_at,current_sign_in_ip,last_sign_in_ip,institution_id,encrypted_api_secret_key,password_changed_at,encrypted_otp_secret,encrypted_otp_secret_iv,encrypted_otp_secret_salt,encrypted_otp_sent_at,consumed_timestep,otp_required_for_login,deactivated_at,enabled_two_factor,confirmed_two_factor,otp_backup_codes,authy_id,last_sign_in_with_authy,authy_status,email_verified,initial_password_updated,force_password_update,account_confirmed,grace_period,awaiting_second_factor,role -4,Inactive User,inactive@inst1.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,1/15/21 13:49,FALSE,FALSE,"{code1,code2,code3}",,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,none -5,Inst Two Admin,admin@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin -7,Inst Two User,user@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user -2,Inst One Admin,admin@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_admin -3,Inst One User,user@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$raEJqJ7eRcEwWmeoiJ2vxenR8dqVXCI1SU9zcgkrxeS.6/haWGi4K,,,,1,9/10/21 14:22,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_user -1,APTrust System,system@aptrust.org,14345551212,1/12/21 17:14,9/10/21 14:24,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,1,9/10/21 14:24,,127.0.0.1,,1,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,admin -6,Two Factor SMS User,sms_user@example.com,12125551212,9/10/21 14:25,9/10/21 14:25,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,,,,,,,,TRUE,,,,,,,,,,TRUE,TRUE,11/9/21 5:00,FALSE,institutional_user -8,Test.edu Admin,admin@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin -9,Test.edu User,user@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user \ No newline at end of file +id,name,email,phone_number,created_at,updated_at,encrypted_password,reset_password_token,reset_password_sent_at,remember_created_at,sign_in_count,current_sign_in_at,last_sign_in_at,current_sign_in_ip,last_sign_in_ip,institution_id,encrypted_api_secret_key,password_changed_at,encrypted_otp_secret,encrypted_otp_secret_iv,encrypted_otp_secret_salt,encrypted_otp_sent_at,consumed_timestep,otp_required_for_login,deactivated_at,enabled_two_factor,confirmed_two_factor,otp_backup_codes,authy_id,last_sign_in_with_authy,authy_status,email_verified,initial_password_updated,force_password_update,account_confirmed,grace_period,awaiting_second_factor,role,encrypted_passkey_session +4,Inactive User,inactive@inst1.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,1/15/21 13:49,FALSE,FALSE,"{code1,code2,code3}",,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,none, +5,Inst Two Admin,admin@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin, +7,Inst Two User,user@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user, +2,Inst One Admin,admin@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_admin, +3,Inst One User,user@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$raEJqJ7eRcEwWmeoiJ2vxenR8dqVXCI1SU9zcgkrxeS.6/haWGi4K,,,,1,9/10/21 14:22,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_user, +1,APTrust System,system@aptrust.org,14345551212,1/12/21 17:14,9/10/21 14:24,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,1,9/10/21 14:24,,127.0.0.1,,1,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,admin, +6,Two Factor SMS User,sms_user@example.com,12125551212,9/10/21 14:25,9/10/21 14:25,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,,,,,,,,TRUE,,,,,,,,,,TRUE,TRUE,11/9/21 5:00,FALSE,institutional_user, +8,Test.edu Admin,admin@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin, +9,Test.edu User,user@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user, \ No newline at end of file diff --git a/db/migrations/014_add_encrypted_passkey_session.sql b/db/migrations/014_add_encrypted_passkey_session.sql new file mode 100644 index 00000000..b8717439 --- /dev/null +++ b/db/migrations/014_add_encrypted_passkey_session.sql @@ -0,0 +1,12 @@ +-- 014_add_encrypted_passkey_session.sql +-- + +-- Note that we're starting the migration. +insert into schema_migrations ("version", started_at) values ('014_add_encrypted_passkey_session', now()) +on conflict ("version") do update set started_at = now(); + +-- Add new POSIX metadata column to generic_files table. +alter table users add column if not exists encrypted_passkey_session varchar null; + +-- Now mark the migration as completed. +update schema_migrations set finished_at = now() where "version" = '014_add_encrypted_passkey_session'; diff --git a/db/schema.sql b/db/schema.sql index 0d324f5b..8a67ffdc 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -501,6 +501,7 @@ CREATE TABLE public.users ( grace_period timestamp NULL, awaiting_second_factor bool NOT NULL DEFAULT false, "role" varchar(50) NOT NULL DEFAULT 'none'::character varying, + encrypted_passkey_session varchar null, CONSTRAINT users_pkey PRIMARY KEY (id) ); CREATE INDEX index_users_on_authy_id ON public.users USING btree (authy_id); diff --git a/forms/lists.go b/forms/lists.go index 428fe180..34191da2 100644 --- a/forms/lists.go +++ b/forms/lists.go @@ -80,6 +80,7 @@ var StorageOptionList = []*ListOption{ var TwoFactorMethodList = []*ListOption{ {constants.TwoFactorNone, "None (Turn Off Two-Factor Authentication)", false}, + {constants.TwoFactorPasskey, "Passkey", false}, {constants.TwoFactorAuthy, "Authy OneTouch", false}, {constants.TwoFactorSMS, "Text Message", false}, } diff --git a/forms/two_factor_setup_form.go b/forms/two_factor_setup_form.go index bd9f8f42..67c83433 100644 --- a/forms/two_factor_setup_form.go +++ b/forms/two_factor_setup_form.go @@ -20,7 +20,7 @@ func NewTwoFactorSetupForm(user *pgmodels.User) *TwoFactorSetupForm { func (f *TwoFactorSetupForm) init() { f.Fields["AuthyStatus"] = &Field{ Name: "AuthyStatus", - Label: "Preferred Method for Two-Factor Auth", + Label: "Authy", Placeholder: "", ErrMsg: "Please choose your preferred method.", Options: TwoFactorMethodList, diff --git a/go.mod b/go.mod index eb0fcd7f..f108ba5f 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/APTrust/registry -go 1.23.0 +go 1.24.0 -toolchain go1.23.3 +toolchain go1.24.5 require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d @@ -14,7 +14,8 @@ require ( github.com/gin-gonic/gin v1.9.1 github.com/go-pg/pg/v10 v10.12.0 github.com/go-redis/redis/v7 v7.4.1 - github.com/google/uuid v1.2.0 + github.com/go-webauthn/webauthn v0.10.2 + github.com/google/uuid v1.6.0 github.com/gorilla/securecookie v1.1.1 github.com/jinzhu/copier v0.3.0 github.com/nsqio/nsq v1.2.0 @@ -22,9 +23,9 @@ require ( github.com/rs/zerolog v1.20.0 github.com/spf13/viper v1.7.1 github.com/stretchr/stew v0.0.0-20130812190256-80ef0842b48b - github.com/stretchr/testify v1.8.3 - golang.org/x/crypto v0.36.0 - golang.org/x/text v0.23.0 + github.com/stretchr/testify v1.11.1 + golang.org/x/crypto v0.41.0 + golang.org/x/text v0.29.0 ) require ( @@ -37,19 +38,23 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-pg/zerochecker v0.2.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-webauthn/x v0.1.9 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gojektech/heimdall v5.0.2+incompatible // indirect github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/protobuf v1.5.0 // indirect github.com/golang/snappy v0.0.3 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/google/go-tpm v0.9.0 // indirect github.com/gorilla/websocket v1.4.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imkira/go-interpol v1.1.0 // indirect @@ -63,7 +68,7 @@ require ( github.com/magiconair/properties v1.8.1 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/nsqio/go-diskqueue v0.0.0-20180306152900-74cfbc9de839 // indirect @@ -88,6 +93,7 @@ require ( github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect github.com/vmihailenco/tagparser v0.1.2 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect @@ -95,8 +101,8 @@ require ( github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/ini.v1 v1.51.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 76495f6b..ad12cd6b 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,8 @@ github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gavv/httpexpect/v2 v2.14.0 h1:rWM60bPJpVcIZWgubYDvipTeHdJlseDM5hovR+wgFVo= @@ -102,6 +104,16 @@ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QX github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI= github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-webauthn/webauthn v0.10.2 h1:OG7B+DyuTytrEPFmTX503K77fqs3HDK/0Iv+z8UYbq4= +github.com/go-webauthn/webauthn v0.10.2/go.mod h1:Gd1IDsGAybuvK1NkwUTLbGmeksxuRJjVN2PE/xsPxHs= +github.com/go-webauthn/webauthn v0.13.0 h1:cJIL1/1l+22UekVhipziAaSgESJxokYkowUqAIsWs0Y= +github.com/go-webauthn/webauthn v0.13.0/go.mod h1:Oy9o2o79dbLKRPZWWgRIOdtBGAhKnDIaBp2PFkICRHs= +github.com/go-webauthn/webauthn v0.14.0 h1:ZLNPUgPcDlAeoxe+5umWG/tEeCoQIDr7gE2Zx2QnhL0= +github.com/go-webauthn/webauthn v0.14.0/go.mod h1:QZzPFH3LJ48u5uEPAu+8/nWJImoLBWM7iAH/kSVSo6k= +github.com/go-webauthn/x v0.1.9 h1:v1oeLmoaa+gPOaZqUdDentu6Rl7HkSSsmOT6gxEQHhE= +github.com/go-webauthn/x v0.1.9/go.mod h1:pJNMlIMP1SU7cN8HNlKJpLEnFHCygLCvaLZ8a1xeoQA= +github.com/go-webauthn/x v0.1.25 h1:g/0noooIGcz/yCVqebcFgNnGIgBlJIccS+LYAa+0Z88= +github.com/go-webauthn/x v0.1.25/go.mod h1:ieblaPY1/BVCV0oQTsA/VAo08/TWayQuJuo5Q+XxmTY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -112,6 +124,10 @@ github.com/gojektech/heimdall v5.0.2+incompatible h1:mfGLnHNTKN7b1OMTO4ZvL3oT2P1 github.com/gojektech/heimdall v5.0.2+incompatible/go.mod h1:8hRIZ3+Kz0r3GAFI9QrUuvZht8ypg5Rs8schCXioLOo= github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 h1:MO2DsGCZz8phRhLnpFvHEQgTH521sVN/6F2GZTbNO3Q= github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45/go.mod h1:tDYRk1s5Pms6XJjj5m2PxAzmQvaDU8GqDf1u6x7yxKw= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -130,17 +146,23 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk= +github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU= +github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= +github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -230,8 +252,9 @@ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTS github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -331,8 +354,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= @@ -358,6 +382,8 @@ github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vb github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -381,6 +407,8 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -396,8 +424,12 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -442,8 +474,9 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -484,8 +517,12 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -496,8 +533,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/middleware/authorization_map.go b/middleware/authorization_map.go index 77c4b867..2332e6e9 100644 --- a/middleware/authorization_map.go +++ b/middleware/authorization_map.go @@ -114,6 +114,8 @@ var AuthMap = map[string]AuthMetadata{ "StorageRecordNew": {"StorageRecord", constants.StorageRecordCreate, "New Storage Record"}, "StorageRecordShow": {"StorageRecord", constants.StorageRecordRead, "Storage Record Detail"}, "StorageRecordUpdate": {"StorageRecord", constants.StorageRecordUpdate, "Update Storage Record"}, + "UserBeginLoginWithPasskey": {"User", constants.UserBeginLoginWithPasskey, "Login With Passkey"}, + "UserBeginPasskeyRegistration": {"User", constants.UserBeginPasskeyRegistration, "Set up a Passkey"}, "UserChangePassword": {"User", constants.UserUpdateSelf, "Change Password"}, "UserComplete2FASetup": {"User", constants.UserComplete2FASetup, "Setup Two-Factor Authentication"}, "UserConfirmPhone": {"User", constants.UserConfirmPhone, "Confirm Phone Number"}, @@ -121,6 +123,8 @@ var AuthMap = map[string]AuthMetadata{ "UserDelete": {"User", constants.UserDelete, "Deactivate User"}, "UserDeleteSelf": {"User", constants.UserDeleteSelf, "Deactivate Your Account"}, "UserEdit": {"User", constants.UserUpdate, "Edit User"}, + "UserFinishLoginWithPasskey": {"User", constants.UserFinishLoginWithPasskey, "Complete Passkey Login"}, + "UserFinishPasskeyRegistration": {"User", constants.UserFinishPasskeyRegistration, "Complete Passkey Setup"}, "UserGenerateBackupCodes": {"User", constants.UserGenerateBackupCodes, "Create Two-Factor Backup Codes"}, "UserGetAPIKey": {"User", constants.UserUpdateSelf, "Generate API Key"}, "UserIndex": {"User", constants.UserRead, "Users"}, diff --git a/network/webauthn.go b/network/webauthn.go new file mode 100644 index 00000000..7c08acd3 --- /dev/null +++ b/network/webauthn.go @@ -0,0 +1,18 @@ +package network + +import ( + "github.com/go-webauthn/webauthn/webauthn" +) + +func NewWebAuthn() *webauthn.WebAuthn { + wconfig := &webauthn.Config{ + RPDisplayName: "APTrust", + RPID: "localhost", + RPOrigins: []string{"http://localhost:8080"}, + } + webauthn, err := webauthn.New(wconfig) + if err != nil { + return nil + } + return webauthn +} diff --git a/notes.md b/notes.md index 20d34ab4..9c16ec2c 100644 --- a/notes.md +++ b/notes.md @@ -102,11 +102,14 @@ Remember, depdenency hell and mountains of garbage code are only one npm package ### Login * Email/password login +* Two-factor passkey * Two-factor text/sms * Two-factor Authy To ensure users won't have to change their passwords when moving from the Rails app, implement the same password encryption scheme as Devise. The scheme is described [here](https://www.freecodecamp.org/news/how-does-devise-keep-your-passwords-safe-d367f6e816eb/), and the [Go bcrypt library](https://pkg.go.dev/golang.org/x/crypto/bcrypt) should be able to support it. +We now support logging in using a device passkey as a second factor of authentication. The user's browser and device must be compatible with passkeys to use this feature. + For two-factor auth, since we're already using Authy, try the [Go Client for Authy](https://github.com/dcu/go-authy). ### Edit @@ -345,6 +348,8 @@ The term "items" below refers to Intellectual Objects, Generic Files, Checksums, # Two Factor Authentication +Passkeys are supported as a second factor of authentication. Note that your browser and device must be compatible with passkeys in order to use them. Passkeys are tied to the device they are configured on. + Current Pharos users who have enabled two-factor authentication receive one-time passwords through SMS or push notifications through Authy OneTouch. These methods were chosen after long discussion with depositors and we cannot change them without another long discussion. So for now, we're sticking with these two. Notes on two-factor setup and workflow have grown large enoug to warrant their own document. See [Two Factor Notes](two_factor_notes.md). diff --git a/pgmodels/user.go b/pgmodels/user.go index 0917ced3..2b4f0b21 100644 --- a/pgmodels/user.go +++ b/pgmodels/user.go @@ -197,6 +197,14 @@ type User struct { // Institution is where they lock you up after you've spent too much // time trying to figure out the old Rails code. Institution *Institution `json:"institution" pg:"rel:has-one"` + + // EncryptedPasskeySession saves session data for use with passkey + // authentication. The passkey is used as a possible second factor of + // authentication for Registry. + EncryptedPasskeySession string `json:"-" form:"-" pg:"encrypted_passkey_session"` + + // EncryptedPasskeyCredential saves the user's device passkey + EncryptedPasskeyCredential string `json:"-" form:"-" pg:"encrypted_passkey_credential"` } // UserByID returns the institution with the specified id. @@ -377,6 +385,12 @@ func (user *User) IsAuthyOneTouchUser() bool { return user.IsTwoFactorUser() && (user.AuthyStatus == constants.TwoFactorAuthy) } +// IsPasskeyUser returns true if the user has enabled Passkeys +// for two-factor login. +func (user *User) IsPasskeyUser() bool { + return user.IsTwoFactorUser() && (user.AuthyStatus == constants.TwoFactorPasskey) +} + // IsTwoFactorUser returns true if this user has enabled and confirmed // two factor authentication. // @@ -401,7 +415,10 @@ func (user *User) TwoFactorMethod() string { if user.IsSMSUser() { return constants.TwoFactorSMS } - return constants.TwoFactorAuthy + if user.IsAuthyOneTouchUser() { + return constants.TwoFactorAuthy + } + return constants.TwoFactorPasskey } // CreateOTPToken creates a new one-time password token, typically diff --git a/static/js/simplewebauthn.index.umd.min.js b/static/js/simplewebauthn.index.umd.min.js new file mode 100644 index 00000000..1af4f403 --- /dev/null +++ b/static/js/simplewebauthn.index.umd.min.js @@ -0,0 +1,2 @@ +/* [@simplewebauthn/browser@10.0.0] */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).SimpleWebAuthnBrowser={})}(this,(function(e){"use strict";function t(e){const t=new Uint8Array(e);let r="";for(const e of t)r+=String.fromCharCode(e);return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function r(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),r=(4-t.length%4)%4,n=t.padEnd(t.length+r,"="),o=atob(n),i=new ArrayBuffer(o.length),a=new Uint8Array(i);for(let e=0;ee(!1)));const e=window.PublicKeyCredential;return void 0===e.isConditionalMediationAvailable?new Promise((e=>e(!1))):e.isConditionalMediationAvailable()}e.WebAuthnAbortService=s,e.WebAuthnError=a,e.base64URLStringToBuffer=r,e.browserSupportsWebAuthn=n,e.browserSupportsWebAuthnAutofill=d,e.bufferToBase64URLString=t,e.platformAuthenticatorIsAvailable=function(){return n()?PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable():new Promise((e=>e(!1)))},e.startAuthentication=async function(e,c=!1){if(!n())throw new Error("WebAuthn is not supported in this browser");let u;0!==e.allowCredentials?.length&&(u=e.allowCredentials?.map(o));const h={...e,challenge:r(e.challenge),allowCredentials:u},f={};if(c){if(!await d())throw Error("Browser does not support WebAuthn autofill");if(document.querySelectorAll("input[autocomplete$='webauthn']").length<1)throw Error('No with "webauthn" as the only or last value in its `autocomplete` attribute was detected');f.mediation="conditional",h.allowCredentials=[]}let p;f.publicKey=h,f.signal=s.createNewAbortSignal();try{p=await navigator.credentials.get(f)}catch(e){throw function({error:e,options:t}){const{publicKey:r}=t;if(!r)throw Error("options was missing required publicKey property");if("AbortError"===e.name){if(t.signal instanceof AbortSignal)return new a({message:"Authentication ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:e})}else{if("NotAllowedError"===e.name)return new a({message:e.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:e});if("SecurityError"===e.name){const t=window.location.hostname;if(!i(t))return new a({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:e});if(r.rpId!==t)return new a({message:`The RP ID "${r.rpId}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:e})}else if("UnknownError"===e.name)return new a({message:"The authenticator was unable to process the specified options, or could not create a new assertion signature",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:e})}return e}({error:e,options:f})}if(!p)throw new Error("Authentication was not completed");const{id:R,rawId:w,response:E,type:g}=p;let A;return E.userHandle&&(A=t(E.userHandle)),{id:R,rawId:t(w),response:{authenticatorData:t(E.authenticatorData),clientDataJSON:t(E.clientDataJSON),signature:t(E.signature),userHandle:A},type:g,clientExtensionResults:p.getClientExtensionResults(),authenticatorAttachment:l(p.authenticatorAttachment)}},e.startRegistration=async function(e){if(!n())throw new Error("WebAuthn is not supported in this browser");const c={publicKey:{...e,challenge:r(e.challenge),user:{...e.user,id:r(e.user.id)},excludeCredentials:e.excludeCredentials?.map(o)}};let d;c.signal=s.createNewAbortSignal();try{d=await navigator.credentials.create(c)}catch(e){throw function({error:e,options:t}){const{publicKey:r}=t;if(!r)throw Error("options was missing required publicKey property");if("AbortError"===e.name){if(t.signal instanceof AbortSignal)return new a({message:"Registration ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:e})}else if("ConstraintError"===e.name){if(!0===r.authenticatorSelection?.requireResidentKey)return new a({message:"Discoverable credentials were required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",cause:e});if("required"===r.authenticatorSelection?.userVerification)return new a({message:"User verification was required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",cause:e})}else{if("InvalidStateError"===e.name)return new a({message:"The authenticator was previously registered",code:"ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",cause:e});if("NotAllowedError"===e.name)return new a({message:e.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:e});if("NotSupportedError"===e.name)return 0===r.pubKeyCredParams.filter((e=>"public-key"===e.type)).length?new a({message:'No entry in pubKeyCredParams was of type "public-key"',code:"ERROR_MALFORMED_PUBKEYCREDPARAMS",cause:e}):new a({message:"No available authenticator supported any of the specified pubKeyCredParams algorithms",code:"ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",cause:e});if("SecurityError"===e.name){const t=window.location.hostname;if(!i(t))return new a({message:`${window.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:e});if(r.rp.id!==t)return new a({message:`The RP ID "${r.rp.id}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:e})}else if("TypeError"===e.name){if(r.user.id.byteLength<1||r.user.id.byteLength>64)return new a({message:"User ID was not between 1 and 64 characters",code:"ERROR_INVALID_USER_ID_LENGTH",cause:e})}else if("UnknownError"===e.name)return new a({message:"The authenticator was unable to process the specified options, or could not create a new credential",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:e})}return e}({error:e,options:c})}if(!d)throw new Error("Registration was not completed");const{id:h,rawId:f,response:p,type:R}=d;let w,E,g,A;if("function"==typeof p.getTransports&&(w=p.getTransports()),"function"==typeof p.getPublicKeyAlgorithm)try{E=p.getPublicKeyAlgorithm()}catch(e){u("getPublicKeyAlgorithm()",e)}if("function"==typeof p.getPublicKey)try{const e=p.getPublicKey();null!==e&&(g=t(e))}catch(e){u("getPublicKey()",e)}if("function"==typeof p.getAuthenticatorData)try{A=t(p.getAuthenticatorData())}catch(e){u("getAuthenticatorData()",e)}return{id:h,rawId:t(f),response:{attestationObject:t(p.attestationObject),clientDataJSON:t(p.clientDataJSON),transports:w,publicKeyAlgorithm:E,publicKey:g,authenticatorData:A},type:R,clientExtensionResults:d.getClientExtensionResults(),authenticatorAttachment:l(d.authenticatorAttachment)}},Object.defineProperty(e,"__esModule",{value:!0})})); diff --git a/views/users/choose_second_factor.html b/views/users/choose_second_factor.html index 073d8952..105f3640 100644 --- a/views/users/choose_second_factor.html +++ b/views/users/choose_second_factor.html @@ -25,6 +25,9 @@

Multi-Factor Authentication Required