Browse Source

Support webauthn (#17957)

Migrate from U2F to Webauthn

Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
pull/18276/head
Lunny Xiao 4 months ago committed by GitHub
parent
commit
35c3553870
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .eslintrc
  2. 5
      cmd/serv.go
  3. 5
      custom/conf/app.example.ini
  4. 5
      docs/content/doc/advanced/config-cheat-sheet.en-us.md
  5. 7
      go.mod
  6. 20
      go.sum
  7. 2
      models/auth/main_test.go
  8. 154
      models/auth/u2f.go
  9. 100
      models/auth/u2f_test.go
  10. 222
      models/auth/webauthn.go
  11. 69
      models/auth/webauthn_test.go
  12. 7
      models/fixtures/u2f_registration.yml
  13. 8
      models/fixtures/webauthn_credential.yml
  14. 2
      models/migrations/migrations.go
  15. 91
      models/migrations/v207.go
  16. 78
      modules/auth/webauthn/webauthn.go
  17. 26
      modules/auth/webauthn/webauthn_test.go
  18. 2
      modules/generate/generate.go
  19. 11
      modules/setting/setting.go
  20. 40
      options/locale/locale_en-US.ini
  21. 11
      package-lock.json
  22. 1
      package.json
  23. 1
      public/vendor/plugins/u2f/index.js
  24. 14
      routers/web/auth/auth.go
  25. 6
      routers/web/auth/linkaccount.go
  26. 20
      routers/web/auth/oauth.go
  27. 2
      routers/web/auth/oauth_test.go
  28. 136
      routers/web/auth/u2f.go
  29. 169
      routers/web/auth/webauthn.go
  30. 5
      routers/web/user/setting/security/security.go
  31. 111
      routers/web/user/setting/security/u2f.go
  32. 119
      routers/web/user/setting/security/webauthn.go
  33. 21
      routers/web/web.go
  34. 5
      services/auth/auth.go
  35. 2
      services/auth/source/oauth2/jwtsigningkey.go
  36. 4
      services/auth/source/oauth2/token.go
  37. 12
      services/forms/user_form.go
  38. 3
      services/lfs/server.go
  39. 3
      templates/base/footer.tmpl
  40. 24
      templates/user/auth/u2f.tmpl
  41. 32
      templates/user/auth/u2f_error.tmpl
  42. 22
      templates/user/auth/webauthn.tmpl
  43. 22
      templates/user/auth/webauthn_error.tmpl
  44. 2
      templates/user/settings/security/security.tmpl
  45. 23
      templates/user/settings/security/webauthn.tmpl
  46. 24
      vendor/github.com/cloudflare/cfssl/LICENSE
  47. 188
      vendor/github.com/cloudflare/cfssl/crypto/pkcs7/pkcs7.go
  48. 46
      vendor/github.com/cloudflare/cfssl/errors/doc.go
  49. 438
      vendor/github.com/cloudflare/cfssl/errors/error.go
  50. 47
      vendor/github.com/cloudflare/cfssl/errors/http.go
  51. 48
      vendor/github.com/cloudflare/cfssl/helpers/derhelpers/derhelpers.go
  52. 133
      vendor/github.com/cloudflare/cfssl/helpers/derhelpers/ed25519.go
  53. 590
      vendor/github.com/cloudflare/cfssl/helpers/helpers.go
  54. 162
      vendor/github.com/cloudflare/cfssl/log/log.go
  55. 336
      vendor/github.com/cloudflare/cfssl/revoke/revoke.go
  56. 26
      vendor/github.com/duo-labs/webauthn/LICENSE.txt
  57. 617
      vendor/github.com/duo-labs/webauthn/metadata/metadata.go
  58. 155
      vendor/github.com/duo-labs/webauthn/protocol/assertion.go
  59. 156
      vendor/github.com/duo-labs/webauthn/protocol/attestation.go
  60. 227
      vendor/github.com/duo-labs/webauthn/protocol/attestation_androidkey.go
  61. 104
      vendor/github.com/duo-labs/webauthn/protocol/attestation_apple.go
  62. 278
      vendor/github.com/duo-labs/webauthn/protocol/attestation_packed.go
  63. 143
      vendor/github.com/duo-labs/webauthn/protocol/attestation_safetynet.go
  64. 349
      vendor/github.com/duo-labs/webauthn/protocol/attestation_tpm.go
  65. 135
      vendor/github.com/duo-labs/webauthn/protocol/attestation_u2f.go
  66. 256
      vendor/github.com/duo-labs/webauthn/protocol/authenticator.go
  67. 41
      vendor/github.com/duo-labs/webauthn/protocol/base64.go
  68. 27
      vendor/github.com/duo-labs/webauthn/protocol/challenge.go
  69. 112
      vendor/github.com/duo-labs/webauthn/protocol/client.go
  70. 216
      vendor/github.com/duo-labs/webauthn/protocol/credential.go
  71. 8
      vendor/github.com/duo-labs/webauthn/protocol/doc.go
  72. 48
      vendor/github.com/duo-labs/webauthn/protocol/entities.go
  73. 85
      vendor/github.com/duo-labs/webauthn/protocol/errors.go
  74. 8
      vendor/github.com/duo-labs/webauthn/protocol/extensions.go
  75. 282
      vendor/github.com/duo-labs/webauthn/protocol/googletpm/certinfo.go
  76. 152
      vendor/github.com/duo-labs/webauthn/protocol/googletpm/googletpm.go
  77. 240
      vendor/github.com/duo-labs/webauthn/protocol/googletpm/pubarea.go
  78. 136
      vendor/github.com/duo-labs/webauthn/protocol/options.go
  79. 1
      vendor/github.com/duo-labs/webauthn/protocol/signature_algorithms.go
  80. 12
      vendor/github.com/duo-labs/webauthn/protocol/webauthncose/ed25519.go
  81. 38
      vendor/github.com/duo-labs/webauthn/protocol/webauthncose/ed25519_go112.go
  82. 400
      vendor/github.com/duo-labs/webauthn/protocol/webauthncose/webauthncose.go
  83. 51
      vendor/github.com/duo-labs/webauthn/webauthn/authenticator.go
  84. 35
      vendor/github.com/duo-labs/webauthn/webauthn/credential.go
  85. 3
      vendor/github.com/duo-labs/webauthn/webauthn/doc.go
  86. 188
      vendor/github.com/duo-labs/webauthn/webauthn/login.go
  87. 71
      vendor/github.com/duo-labs/webauthn/webauthn/main.go
  88. 170
      vendor/github.com/duo-labs/webauthn/webauthn/registration.go
  89. 13
      vendor/github.com/duo-labs/webauthn/webauthn/session.go
  90. 42
      vendor/github.com/duo-labs/webauthn/webauthn/user.go
  91. 12
      vendor/github.com/fxamacker/cbor/v2/.gitignore
  92. 86
      vendor/github.com/fxamacker/cbor/v2/.golangci.yml
  93. 264
      vendor/github.com/fxamacker/cbor/v2/CBOR_BENCHMARKS.md
  94. 32
      vendor/github.com/fxamacker/cbor/v2/CBOR_GOLANG.md
  95. 76
      vendor/github.com/fxamacker/cbor/v2/CODE_OF_CONDUCT.md
  96. 47
      vendor/github.com/fxamacker/cbor/v2/CONTRIBUTING.md
  97. 21
      vendor/github.com/fxamacker/cbor/v2/LICENSE
  98. 938
      vendor/github.com/fxamacker/cbor/v2/README.md
  99. 308
      vendor/github.com/fxamacker/cbor/v2/cache.go
  100. 1642
      vendor/github.com/fxamacker/cbor/v2/decode.go
  101. Some files were not shown because too many files have changed in this diff Show More

1
.eslintrc

@ -27,7 +27,6 @@ globals: @@ -27,7 +27,6 @@ globals:
CodeMirror: false
Dropzone: false
SimpleMDE: false
u2fApi: false
settings:
html/html-extensions: [".tmpl"]

5
cmd/serv.go

@ -27,7 +27,7 @@ import ( @@ -27,7 +27,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/lfs"
"github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v4"
"github.com/kballard/go-shellquote"
"github.com/urfave/cli"
)
@ -253,7 +253,8 @@ func runServ(c *cli.Context) error { @@ -253,7 +253,8 @@ func runServ(c *cli.Context) error {
now := time.Now()
claims := lfs.Claims{
StandardClaims: jwt.StandardClaims{
// FIXME: we need to migrate to RegisteredClaims
StandardClaims: jwt.StandardClaims{ // nolint
ExpiresAt: now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
NotBefore: now.Unix(),
},

5
custom/conf/app.example.ini

@ -435,9 +435,10 @@ ENABLE = true @@ -435,9 +435,10 @@ ENABLE = true
;; NOTE: THE DEFAULT VALUES HERE WILL NEED TO BE CHANGED
;; Two Factor authentication with security keys
;; https://developers.yubico.com/U2F/App_ID.html
;;
;; DEPRECATED - this only applies to previously registered security keys using the U2F standard
APP_ID = ; e.g. http://localhost:3000/
;; Comma separated list of trusted facets
TRUSTED_FACETS = ; e.g. http://localhost:3000/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

5
docs/content/doc/advanced/config-cheat-sheet.en-us.md

@ -974,9 +974,8 @@ NB: You must have `DISABLE_ROUTER_LOG` set to `false` for this option to take ef @@ -974,9 +974,8 @@ NB: You must have `DISABLE_ROUTER_LOG` set to `false` for this option to take ef
- `LANGS`: **en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID,ml-IN**: List of locales shown in language selector
- `NAMES`: **English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,日本語,español,português do Brasil,Português de Portugal,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어,ελληνικά,فارسی,magyar nyelv,bahasa Indonesia,മലയ**: Visible names corresponding to the locales
## U2F (`U2F`)
- `APP_ID`: **`ROOT_URL`**: Declares the facet of the application. Requires HTTPS.
- `TRUSTED_FACETS`: List of additional facets which are trusted. This is not support by all browsers.
## U2F (`U2F`) **DEPRECATED**
- `APP_ID`: **`ROOT_URL`**: Declares the facet of the application which is used for authentication of previously registered U2F keys. Requires HTTPS.
## Markup (`markup`)

7
go.mod

@ -30,6 +30,7 @@ require ( @@ -30,6 +30,7 @@ require (
github.com/denisenkom/go-mssqldb v0.10.0
github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
github.com/dustin/go-humanize v1.0.0
github.com/editorconfig/editorconfig-core-go/v2 v2.4.2
github.com/emirpasic/gods v1.12.0
@ -50,7 +51,7 @@ require ( @@ -50,7 +51,7 @@ require (
github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v4 v4.2.0
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-github/v39 v39.2.0
github.com/google/uuid v1.2.0
@ -140,6 +141,8 @@ require ( @@ -140,6 +141,8 @@ require (
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/golang-jwt/jwt v3.2.1+incompatible => github.com/golang-jwt/jwt v3.2.2+incompatible
replace github.com/markbates/goth v1.68.0 => github.com/zeripath/goth v1.68.1-0.20220109111530-754359885dce
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/duo-labs/webauthn => github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4

20
go.sum

@ -131,6 +131,8 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o @@ -131,6 +131,8 @@ github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:o
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4 h1:u3eFvgr4A8IjlAokbFt6XY6VdurX7DEYnQMQ4K2yobc=
github.com/authelia/webauthn v0.0.0-20211225121951-80d1f2a572e4/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
@ -209,6 +211,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P @@ -209,6 +211,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 h1:Puu1hUwfps3+1CUzYdAZXijuvLuRMirgiXdf3zsM2Ig=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@ -303,6 +307,8 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2 @@ -303,6 +307,8 @@ github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2
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.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.3.3 h1:mBQ8NiOgDkINJrZtoizkC3nDNYgSaWtxyem6S2XHBtA=
@ -498,8 +504,9 @@ github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQ @@ -498,8 +504,9 @@ github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQ
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:UjoPNDAQ5JPCjlxoJd6K8ALZqSDDhk2ymieAZOVaDg0=
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -540,6 +547,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -540,6 +547,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
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.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -819,8 +828,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 @@ -819,8 +828,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/markbates/goth v1.68.0 h1:90sKvjRAKHcl9V2uC9x/PJXeD78cFPiBsyP1xVhoQfA=
github.com/markbates/goth v1.68.0/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
@ -1034,6 +1041,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf @@ -1034,6 +1041,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@ -1141,6 +1149,8 @@ github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= @@ -1141,6 +1149,8 @@ github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
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/xanzy/go-gitlab v0.50.1 h1:eH1G0/ZV1j81rhGrtbcePjbM5Ern7mPA4Xjt+yE+2PQ=
github.com/xanzy/go-gitlab v0.50.1/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE=
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
@ -1169,6 +1179,8 @@ github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01/go.mod @@ -1169,6 +1179,8 @@ github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01/go.mod
github.com/yuin/goldmark-meta v1.0.0 h1:ScsatUIT2gFS6azqzLGUjgOnELsBOxMXerM3ogdJhAM=
github.com/yuin/goldmark-meta v1.0.0/go.mod h1:zsNNOrZ4nLuyHAJeLQEZcQat8dm70SmB2kHbls092Gc=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/zeripath/goth v1.68.1-0.20220109111530-754359885dce h1:ul/k+Fu3/2h+hxIaEMrn6m96X1Wf+TQk9G7zyuvy1Ws=
github.com/zeripath/goth v1.68.1-0.20220109111530-754359885dce/go.mod h1:uk3KIdtCKdmyNABgOSmHFNHN0AcKqkLs8j5Ak3Ioe1Q=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=

2
models/auth/main_test.go

@ -17,6 +17,6 @@ func TestMain(m *testing.M) { @@ -17,6 +17,6 @@ func TestMain(m *testing.M) {
"oauth2_application.yml",
"oauth2_authorization_code.yml",
"oauth2_grant.yml",
"u2f_registration.yml",
"webauthn_credential.yml",
)
}

154
models/auth/u2f.go

@ -1,154 +0,0 @@ @@ -1,154 +0,0 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"github.com/tstranex/u2f"
)
// ____ ________________________________ .__ __ __ .__
// | | \_____ \_ _____/\______ \ ____ ____ |__| _______/ |_____________ _/ |_|__| ____ ____
// | | // ____/| __) | _// __ \ / ___\| |/ ___/\ __\_ __ \__ \\ __\ |/ _ \ / \
// | | // \| \ | | \ ___// /_/ > |\___ \ | | | | \// __ \| | | ( <_> ) | \
// |______/ \_______ \___ / |____|_ /\___ >___ /|__/____ > |__| |__| (____ /__| |__|\____/|___| /
// \/ \/ \/ \/_____/ \/ \/ \/
// ErrU2FRegistrationNotExist represents a "ErrU2FRegistrationNotExist" kind of error.
type ErrU2FRegistrationNotExist struct {
ID int64
}
func (err ErrU2FRegistrationNotExist) Error() string {
return fmt.Sprintf("U2F registration does not exist [id: %d]", err.ID)
}
// IsErrU2FRegistrationNotExist checks if an error is a ErrU2FRegistrationNotExist.
func IsErrU2FRegistrationNotExist(err error) bool {
_, ok := err.(ErrU2FRegistrationNotExist)
return ok
}
// U2FRegistration represents the registration data and counter of a security key
type U2FRegistration struct {
ID int64 `xorm:"pk autoincr"`
Name string
UserID int64 `xorm:"INDEX"`
Raw []byte
Counter uint32 `xorm:"BIGINT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
func init() {
db.RegisterModel(new(U2FRegistration))
}
// TableName returns a better table name for U2FRegistration
func (reg U2FRegistration) TableName() string {
return "u2f_registration"
}
// Parse will convert the db entry U2FRegistration to an u2f.Registration struct
func (reg *U2FRegistration) Parse() (*u2f.Registration, error) {
r := new(u2f.Registration)
return r, r.UnmarshalBinary(reg.Raw)
}
func (reg *U2FRegistration) updateCounter(e db.Engine) error {
_, err := e.ID(reg.ID).Cols("counter").Update(reg)
return err
}
// UpdateCounter will update the database value of counter
func (reg *U2FRegistration) UpdateCounter() error {
return reg.updateCounter(db.GetEngine(db.DefaultContext))
}
// U2FRegistrationList is a list of *U2FRegistration
type U2FRegistrationList []*U2FRegistration
// ToRegistrations will convert all U2FRegistrations to u2f.Registrations
func (list U2FRegistrationList) ToRegistrations() []u2f.Registration {
regs := make([]u2f.Registration, 0, len(list))
for _, reg := range list {
r, err := reg.Parse()
if err != nil {
log.Error("parsing u2f registration: %v", err)
continue
}
regs = append(regs, *r)
}
return regs
}
func getU2FRegistrationsByUID(e db.Engine, uid int64) (U2FRegistrationList, error) {
regs := make(U2FRegistrationList, 0)
return regs, e.Where("user_id = ?", uid).Find(&regs)
}
// GetU2FRegistrationByID returns U2F registration by id
func GetU2FRegistrationByID(id int64) (*U2FRegistration, error) {
return getU2FRegistrationByID(db.GetEngine(db.DefaultContext), id)
}
func getU2FRegistrationByID(e db.Engine, id int64) (*U2FRegistration, error) {
reg := new(U2FRegistration)
if found, err := e.ID(id).Get(reg); err != nil {
return nil, err
} else if !found {
return nil, ErrU2FRegistrationNotExist{ID: id}
}
return reg, nil
}
// GetU2FRegistrationsByUID returns all U2F registrations of the given user
func GetU2FRegistrationsByUID(uid int64) (U2FRegistrationList, error) {
return getU2FRegistrationsByUID(db.GetEngine(db.DefaultContext), uid)
}
// HasU2FRegistrationsByUID returns whether a given user has U2F registrations
func HasU2FRegistrationsByUID(uid int64) (bool, error) {
return db.GetEngine(db.DefaultContext).Where("user_id = ?", uid).Exist(&U2FRegistration{})
}
func createRegistration(e db.Engine, userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) {
raw, err := reg.MarshalBinary()
if err != nil {
return nil, err
}
r := &U2FRegistration{
UserID: userID,
Name: name,
Counter: 0,
Raw: raw,
}
_, err = e.InsertOne(r)
if err != nil {
return nil, err
}
return r, nil
}
// CreateRegistration will create a new U2FRegistration from the given Registration
func CreateRegistration(userID int64, name string, reg *u2f.Registration) (*U2FRegistration, error) {
return createRegistration(db.GetEngine(db.DefaultContext), userID, name, reg)
}
// DeleteRegistration will delete U2FRegistration
func DeleteRegistration(reg *U2FRegistration) error {
return deleteRegistration(db.GetEngine(db.DefaultContext), reg)
}
func deleteRegistration(e db.Engine, reg *U2FRegistration) error {
_, err := e.Delete(reg)
return err
}

100
models/auth/u2f_test.go

@ -1,100 +0,0 @@ @@ -1,100 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"encoding/hex"
"testing"
"code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
"github.com/tstranex/u2f"
)
func TestGetU2FRegistrationByID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
res, err := GetU2FRegistrationByID(1)
assert.NoError(t, err)
assert.Equal(t, "U2F Key", res.Name)
_, err = GetU2FRegistrationByID(342432)
assert.Error(t, err)
assert.True(t, IsErrU2FRegistrationNotExist(err))
}
func TestGetU2FRegistrationsByUID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
res, err := GetU2FRegistrationsByUID(32)
assert.NoError(t, err)
assert.Len(t, res, 1)
assert.Equal(t, "U2F Key", res[0].Name)
}
func TestU2FRegistration_TableName(t *testing.T) {
assert.Equal(t, "u2f_registration", U2FRegistration{}.TableName())
}
func TestU2FRegistration_UpdateCounter(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
reg := unittest.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
reg.Counter = 1
assert.NoError(t, reg.UpdateCounter())
unittest.AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 1})
}
func TestU2FRegistration_UpdateLargeCounter(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
reg := unittest.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
reg.Counter = 0xffffffff
assert.NoError(t, reg.UpdateCounter())
unittest.AssertExistsIf(t, true, &U2FRegistration{ID: 1, Counter: 0xffffffff})
}
func TestCreateRegistration(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
res, err := CreateRegistration(1, "U2F Created Key", &u2f.Registration{Raw: []byte("Test")})
assert.NoError(t, err)
assert.Equal(t, "U2F Created Key", res.Name)
assert.Equal(t, []byte("Test"), res.Raw)
unittest.AssertExistsIf(t, true, &U2FRegistration{Name: "U2F Created Key", UserID: 1})
}
func TestDeleteRegistration(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
reg := unittest.AssertExistsAndLoadBean(t, &U2FRegistration{ID: 1}).(*U2FRegistration)
assert.NoError(t, DeleteRegistration(reg))
unittest.AssertNotExistsBean(t, &U2FRegistration{ID: 1})
}
const validU2FRegistrationResponseHex = "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871"
func TestToRegistrations_SkipInvalidItemsWithoutCrashing(t *testing.T) {
regKeyRaw, _ := hex.DecodeString(validU2FRegistrationResponseHex)
regs := U2FRegistrationList{
&U2FRegistration{ID: 1},
&U2FRegistration{ID: 2, Name: "U2F Key", UserID: 2, Counter: 0, Raw: regKeyRaw, CreatedUnix: 946684800, UpdatedUnix: 946684800},
}
actual := regs.ToRegistrations()
assert.Len(t, actual, 1)
}
func TestToRegistrations(t *testing.T) {
regKeyRaw, _ := hex.DecodeString(validU2FRegistrationResponseHex)
regs := U2FRegistrationList{
&U2FRegistration{ID: 1, Name: "U2F Key", UserID: 1, Counter: 0, Raw: regKeyRaw, CreatedUnix: 946684800, UpdatedUnix: 946684800},
&U2FRegistration{ID: 2, Name: "U2F Key", UserID: 2, Counter: 0, Raw: regKeyRaw, CreatedUnix: 946684800, UpdatedUnix: 946684800},
}
actual := regs.ToRegistrations()
assert.Len(t, actual, 2)
}

222
models/auth/webauthn.go

@ -0,0 +1,222 @@ @@ -0,0 +1,222 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"context"
"encoding/base64"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
"github.com/duo-labs/webauthn/webauthn"
)
// ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error.
type ErrWebAuthnCredentialNotExist struct {
ID int64
CredentialID string
}
func (err ErrWebAuthnCredentialNotExist) Error() string {
if err.CredentialID == "" {
return fmt.Sprintf("WebAuthn credential does not exist [id: %d]", err.ID)
}
return fmt.Sprintf("WebAuthn credential does not exist [credential_id: %s]", err.CredentialID)
}
//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 string `xorm:"INDEX"`
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() error {
return cred.updateSignCount(db.DefaultContext)
}
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(session *xorm.Session) {
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 {
credID, _ := base64.RawStdEncoding.DecodeString(cred.CredentialID)
creds = append(creds, webauthn.Credential{
ID: credID,
PublicKey: cred.PublicKey,
AttestationType: cred.AttestationType,
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(uid int64) (WebAuthnCredentialList, error) {
return getWebAuthnCredentialsByUID(db.DefaultContext, uid)
}
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(uid int64) (bool, error) {
return existsWebAuthnCredentialsByUID(db.DefaultContext, uid)
}
func existsWebAuthnCredentialsByUID(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(uid int64, name string) (*WebAuthnCredential, error) {
return getWebAuthnCredentialByName(db.DefaultContext, uid, name)
}
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(id int64) (*WebAuthnCredential, error) {
return getWebAuthnCredentialByID(db.DefaultContext, 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(uid int64) (bool, error) {
return db.GetEngine(db.DefaultContext).Where("user_id = ?", uid).Exist(&WebAuthnCredential{})
}
// GetWebAuthnCredentialByCredID returns WebAuthn credential by credential ID
func GetWebAuthnCredentialByCredID(credID string) (*WebAuthnCredential, error) {
return getWebAuthnCredentialByCredID(db.DefaultContext, credID)
}
func getWebAuthnCredentialByCredID(ctx context.Context, credID string) (*WebAuthnCredential, error) {
cred := new(WebAuthnCredential)
if found, err := db.GetEngine(ctx).Where("credential_id = ?", 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(userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) {
return createCredential(db.DefaultContext, userID, name, cred)
}
func createCredential(ctx context.Context, userID int64, name string, cred *webauthn.Credential) (*WebAuthnCredential, error) {
c := &WebAuthnCredential{
UserID: userID,
Name: name,
CredentialID: base64.RawStdEncoding.EncodeToString(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(id, userID int64) (bool, error) {
return deleteCredential(db.DefaultContext, id, userID)
}
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(userID int64) ([]webauthn.Credential, error) {
dbCreds, err := GetWebAuthnCredentialsByUID(userID)
if err != nil {
return nil, err
}
return dbCreds.ToCredentials(), nil
}

69
models/auth/webauthn_test.go

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"encoding/base64"
"testing"
"code.gitea.io/gitea/models/unittest"
"github.com/duo-labs/webauthn/webauthn"
"github.com/stretchr/testify/assert"
)
func TestGetWebAuthnCredentialByID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
res, err := GetWebAuthnCredentialByID(1)
assert.NoError(t, err)
assert.Equal(t, "WebAuthn credential", res.Name)
_, err = GetWebAuthnCredentialByID(342432)
assert.Error(t, err)
assert.True(t, IsErrWebAuthnCredentialNotExist(err))
}
func TestGetWebAuthnCredentialsByUID(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
res, err := GetWebAuthnCredentialsByUID(32)
assert.NoError(t, err)
assert.Len(t, res, 1)
assert.Equal(t, "WebAuthn credential", res[0].Name)
}
func TestWebAuthnCredential_TableName(t *testing.T) {
assert.Equal(t, "webauthn_credential", WebAuthnCredential{}.TableName())
}
func TestWebAuthnCredential_UpdateSignCount(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
cred := unittest.AssertExistsAndLoadBean(t, &WebAuthnCredential{ID: 1}).(*WebAuthnCredential)
cred.SignCount = 1
assert.NoError(t, cred.UpdateSignCount())
unittest.AssertExistsIf(t, true, &WebAuthnCredential{ID: 1, SignCount: 1})
}
func TestWebAuthnCredential_UpdateLargeCounter(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
cred := unittest.AssertExistsAndLoadBean(t, &WebAuthnCredential{ID: 1}).(*WebAuthnCredential)
cred.SignCount = 0xffffffff
assert.NoError(t, cred.UpdateSignCount())
unittest.AssertExistsIf(t, true, &WebAuthnCredential{ID: 1, SignCount: 0xffffffff})
}
func TestCreateCredential(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
res, err := CreateCredential(1, "WebAuthn Created Credential", &webauthn.Credential{ID: []byte("Test")})
assert.NoError(t, err)
assert.Equal(t, "WebAuthn Created Credential", res.Name)
bs, err := base64.RawStdEncoding.DecodeString(res.CredentialID)
assert.NoError(t, err)
assert.Equal(t, []byte("Test"), bs)
unittest.AssertExistsIf(t, true, &WebAuthnCredential{Name: "WebAuthn Created Credential", UserID: 1})
}

7
models/fixtures/u2f_registration.yml

@ -1,7 +0,0 @@ @@ -1,7 +0,0 @@
-
id: 1
name: "U2F Key"
user_id: 32
counter: 0
created_unix: 946684800
updated_unix: 946684800

8
models/fixtures/webauthn_credential.yml

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
- id: 1
name: "WebAuthn credential"
user_id: 32
attestation_type: none
sign_count: 0
clone_warning: false
created_unix: 946684800
updated_unix: 946684800

2
models/migrations/migrations.go

@ -366,6 +366,8 @@ var migrations = []Migration{ @@ -366,6 +366,8 @@ var migrations = []Migration{
NewMigration("Migrate to higher varchar on user struct", migrateUserPasswordSalt),
// v206 -> v207
NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
// v207 -> v208
NewMigration("Add webauthn table and migrate u2f data to webauthn", addWebAuthnCred),
}
// GetCurrentDBVersion returns the current db version

91
models/migrations/v207.go

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"crypto/elliptic"
"encoding/base64"
"strings"
"code.gitea.io/gitea/modules/timeutil"
"github.com/tstranex/u2f"
"xorm.io/xorm"
)
func addWebAuthnCred(x *xorm.Engine) error {
// Create webauthnCredential table
type webauthnCredential struct {
ID int64 `xorm:"pk autoincr"`
Name string
LowerName string `xorm:"unique(s)"`
UserID int64 `xorm:"INDEX unique(s)"`
CredentialID string `xorm:"INDEX"`
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32 `xorm:"BIGINT"`
CloneWarning bool
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
if err := x.Sync2(&webauthnCredential{}); err != nil {
return err
}
// Now migrate the old u2f registrations to the new format
type u2fRegistration struct {
ID int64 `xorm:"pk autoincr"`
Name string
UserID int64 `xorm:"INDEX"`
Raw []byte
Counter uint32 `xorm:"BIGINT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
var start int
regs := make([]*u2fRegistration, 0, 50)
for {
err := x.OrderBy("id").Limit(50, start).Find(&regs)
if err != nil {
return err
}
for _, reg := range regs {
parsed := new(u2f.Registration)
err = parsed.UnmarshalBinary(reg.Raw)
if err != nil {
continue
}
c := &webauthnCredential{
ID: reg.ID,
Name: reg.Name,
LowerName: strings.ToLower(reg.Name),
UserID: reg.UserID,
CredentialID: base64.RawStdEncoding.EncodeToString(parsed.KeyHandle),
PublicKey: elliptic.Marshal(elliptic.P256(), parsed.PubKey.X, parsed.PubKey.Y),
AttestationType: "fido-u2f",
AAGUID: []byte{},
SignCount: reg.Counter,
}
_, err := x.Insert(c)
if err != nil {
return err
}
}
if len(regs) < 50 {
break
}
start += 50
regs = regs[:0]
}
return nil
}

78
modules/auth/webauthn/webauthn.go

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package webauthn
import (
"encoding/binary"
"encoding/gob"
"net/url"
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
)
//WebAuthn represents the global WebAuthn instance
var WebAuthn *webauthn.WebAuthn
//Init initializes the WebAuthn instance from the config.
func Init() {
gob.Register(&webauthn.SessionData{})
appURL, _ := url.Parse(setting.AppURL)
WebAuthn = &webauthn.WebAuthn{
Config: &webauthn.Config{
RPDisplayName: setting.AppName,
RPID: setting.Domain,
RPOrigin: protocol.FullyQualifiedOrigin(appURL),
AuthenticatorSelection: protocol.AuthenticatorSelection{
UserVerification: "discouraged",
},
AttestationPreference: protocol.PreferDirectAttestation,
},
}
}
// User represents an implementation of webauthn.User based on User model
type User user_model.User
//WebAuthnID implements the webauthn.User interface
func (u *User) WebAuthnID() []byte {
id := make([]byte, 8)
binary.PutVarint(id, u.ID)
return id
}
//WebAuthnName implements the webauthn.User interface
func (u *User) WebAuthnName() string {
if u.LoginName == "" {
return u.Name
}
return u.LoginName
}
//WebAuthnDisplayName implements the webauthn.User interface
func (u *User) WebAuthnDisplayName() string {
return (*user_model.User)(u).DisplayName()
}
//WebAuthnIcon implements the webauthn.User interface
func (u *User) WebAuthnIcon() string {
return (*user_model.User)(u).AvatarLink()
}
//WebAuthnCredentials implementns the webauthn.User interface
func (u *User) WebAuthnCredentials() []webauthn.Credential {
dbCreds, err := auth.GetWebAuthnCredentialsByUID(u.ID)
if err != nil {
return nil
}
return dbCreds.ToCredentials()
}

26
modules/auth/webauthn/webauthn_test.go

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package webauthn
import (
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
func TestInit(t *testing.T) {
setting.Domain = "domain"
setting.AppName = "AppName"
setting.AppURL = "https://domain/"
rpOrigin := "https://domain"
Init()
assert.Equal(t, setting.Domain, WebAuthn.Config.RPID)
assert.Equal(t, setting.AppName, WebAuthn.Config.RPDisplayName)
assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigin)
}

2
modules/generate/generate.go

@ -13,7 +13,7 @@ import ( @@ -13,7 +13,7 @@ import (
"code.gitea.io/gitea/modules/util"
"github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v4"
)
// NewInternalToken generate a new value intended to be used by INTERNAL_TOKEN.

11
modules/setting/setting.go

@ -28,7 +28,6 @@ import ( @@ -28,7 +28,6 @@ import (
"code.gitea.io/gitea/modules/user"
"code.gitea.io/gitea/modules/util"
shellquote "github.com/kballard/go-shellquote"
"github.com/unknwon/com"
gossh "golang.org/x/crypto/ssh"
ini "gopkg.in/ini.v1"
@ -388,8 +387,7 @@ var ( @@ -388,8 +387,7 @@ var (
}
U2F = struct {
AppID string
TrustedFacets []string
AppID string
}{}
// Metrics settings
@ -1015,10 +1013,6 @@ func loadFromConf(allowEmpty bool, extraConfig string) { @@ -1015,10 +1013,6 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
newMarkup()
sec = Cfg.Section("U2F")
U2F.TrustedFacets, _ = shellquote.Split(sec.Key("TRUSTED_FACETS").MustString(strings.TrimSuffix(AppURL, AppSubURL+"/")))
U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
UI.ReactionsMap = make(map[string]bool)
for _, reaction := range UI.Reactions {
UI.ReactionsMap[reaction] = true
@ -1027,6 +1021,9 @@ func loadFromConf(allowEmpty bool, extraConfig string) { @@ -1027,6 +1021,9 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
for _, emoji := range UI.CustomEmojis {
UI.CustomEmojisMap[emoji] = ":" + emoji + ":"
}
sec = Cfg.Section("U2F")
U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
}
func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {

40
options/locale/locale_en-US.ini

@ -34,18 +34,20 @@ twofa = Two-Factor Authentication @@ -34,18 +34,20 @@ twofa = Two-Factor Authentication
twofa_scratch = Two-Factor Scratch Code
passcode = Passcode
u2f_insert_key = Insert your security key
u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it.
u2f_press_button = Please press the button on your security key…
u2f_use_twofa = Use a two-factor code from your phone
u2f_error = Could not read your security key.
u2f_unsupported_browser = Your browser does not support U2F security keys.
u2f_error_1 = An unknown error occurred. Please retry.
u2f_error_2 = Please make sure to use the correct, encrypted (https://) URL.
u2f_error_3 = The server could not process your request.
u2f_error_4 = The security key is not permitted for this request. Please make sure that the key is not already registered.
u2f_error_5 = Timeout reached before your key could be read. Please reload this page and retry.
u2f_reload = Reload
webauthn_insert_key = Insert your security key
webauthn_sign_in = Press the button on your security key. If your security key has no button, re-insert it.
webauthn_press_button = Please press the button on your security key…
webauthn_use_twofa = Use a two-factor code from your phone
webauthn_error = Could not read your security key.
webauthn_unsupported_browser = Your browser does not currently support WebAuthn.
webauthn_error_unknown = An unknown error occurred. Please retry.
webauthn_error_insecure = WebAuthn only supports secure connections. For testing over HTTP, you can use the origin "localhost" or "127.0.0.1"
webauthn_error_unable_to_process = The server could not process your request.
webauthn_error_duplicated = The security key is not permitted for this request. Please make sure that the key is not already registered.
webauthn_error_empty = You must set a name for this key.
webauthn_error_timeout = Timeout reached before your key could be read. Please reload this page and retry.
webauthn_u2f_deprecated = The key: '%s' authenticates using the deprecated U2F process. You should re-register this key and remove the old registration.
webauthn_reload = Reload
repository = Repository
organization = Organization
@ -525,7 +527,7 @@ twofa = Two-Factor Authentication @@ -525,7 +527,7 @@ twofa = Two-Factor Authentication
account_link = Linked Accounts
organization = Organizations
uid = Uid
u2f = Security Keys
webauthn = Security Keys
public_profile = Public Profile
biography_placeholder = Tell us a little bit about yourself
@ -746,12 +748,12 @@ passcode_invalid = The passcode is incorrect. Try again. @@ -746,12 +748,12 @@ passcode_invalid = The passcode is incorrect. Try again.
twofa_enrolled = Your account has been enrolled into two-factor authentication. Store your scratch token (%s) in a safe place as it is only shown once!
twofa_failed_get_secret = Failed to get secret.
u2f_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" href="https://fidoalliance.org/">FIDO U2F</a> standard.
u2f_register_key = Add Security Key
u2f_nickname = Nickname
u2f_press_button = Press the button on your security key to register it.
u2f_delete_key = Remove Security Key
u2f_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue?
webauthn_desc = Security keys are hardware devices containing cryptographic keys. They can be used for two-factor authentication. Security keys must support the <a rel="noreferrer" href="https://w3c.github.io/webauthn/#webauthn-authenticator">WebAuthn Authenticator</a> standard.
webauthn_register_key = Add Security Key
webauthn_nickname = Nickname
webauthn_press_button = Press the button on your security key to register it.
webauthn_delete_key = Remove Security Key
webauthn_delete_key_desc = If you remove a security key you can no longer sign in with it. Continue?
manage_account_links = Manage Linked Accounts
manage_account_links_desc = These external accounts are linked to your Gitea account.

11
package-lock.json generated

@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
"sortablejs": "1.14.0",
"swagger-ui-dist": "4.1.3",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
"vue": "2.6.14",
"vue-bar-graph": "1.3.0",
"vue-calendar-heatmap": "0.8.4",
@ -9738,6 +9739,11 @@ @@ -9738,6 +9739,11 @@
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.1.tgz",
"integrity": "sha512-bTGLjbD3WqZDR3CgEFkyi9Q/SS2oM29ipXrWfDb4M74ea69QwKAECVceYpaBu0GfdnASMg9Qfl67ttB23nePHg=="
},
"node_modules/uint8-to-base64": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/uint8-to-base64/-/uint8-to-base64-0.2.0.tgz",
"integrity": "sha512-r13jrghEYZAN99GeYpEjM107DOxqB65enskpwce8rRHVAGEtaWmsF5GqoGdPMf8DIXc9XyAJTdvlvRZi4LsszA=="
},
"node_modules/unbox-primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@ -17882,6 +17888,11 @@ @@ -17882,6 +17888,11 @@
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.1.tgz",
"integrity": "sha512-bTGLjbD3WqZDR3CgEFkyi9Q/SS2oM29ipXrWfDb4M74ea69QwKAECVceYpaBu0GfdnASMg9Qfl67ttB23nePHg=="
},
"uint8-to-base64": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/uint8-to-base64/-/uint8-to-base64-0.2.0.tgz",
"integrity": "sha512-r13jrghEYZAN99GeYpEjM107DOxqB65enskpwce8rRHVAGEtaWmsF5GqoGdPMf8DIXc9XyAJTdvlvRZi4LsszA=="
},
"unbox-primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",

1
package.json

@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
"sortablejs": "1.14.0",
"swagger-ui-dist": "4.1.3",
"tributejs": "5.1.3",
"uint8-to-base64": "0.2.0",
"vue": "2.6.14",
"vue-bar-graph": "1.3.0",
"vue-calendar-heatmap": "0.8.4",

1
public/vendor/plugins/u2f/index.js vendored

File diff suppressed because one or more lines are too long

14
routers/web/auth/auth.go

@ -236,14 +236,14 @@ func SignInPost(ctx *context.Context) { @@ -236,14 +236,14 @@ func SignInPost(ctx *context.Context) {
return
}
// Check if the user has u2f registration
hasU2Ftwofa, err := auth.HasU2FRegistrationsByUID(u.ID)
// Check if the user has webauthn registration
hasWebAuthnTwofa, err := auth.HasWebAuthnRegistrationsByUID(u.ID)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
if !hasTOTPtwofa && !hasU2Ftwofa {
if !hasTOTPtwofa && !hasWebAuthnTwofa {
// No two factor auth configured we can sign in the user
handleSignIn(ctx, u, form.Remember)
return
@ -254,7 +254,7 @@ func SignInPost(ctx *context.Context) { @@ -254,7 +254,7 @@ func SignInPost(ctx *context.Context) {
return
}
// User will need to use 2FA TOTP or U2F, save data
// User will need to use 2FA TOTP or WebAuthn, save data
if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
ctx.ServerError("UserSignIn: Unable to set twofaUid in session", err)
return
@ -268,7 +268,7 @@ func SignInPost(ctx *context.Context) { @@ -268,7 +268,7 @@ func SignInPost(ctx *context.Context) {
if hasTOTPtwofa {
// User will need to use U2F, save data
if err := ctx.Session.Set("totpEnrolled", u.ID); err != nil {
ctx.ServerError("UserSignIn: Unable to set u2fEnrolled in session", err)
ctx.ServerError("UserSignIn: Unable to set WebAuthn Enrolled in session", err)
return
}
}
@ -279,8 +279,8 @@ func SignInPost(ctx *context.Context) { @@ -279,8 +279,8 @@ func SignInPost(ctx *context.Context) {
}
// If we have U2F redirect there first
if hasU2Ftwofa {
ctx.Redirect(setting.AppSubURL + "/user/u2f")
if hasWebAuthnTwofa {
ctx.Redirect(setting.AppSubURL + "/user/webauthn")
return
}

6
routers/web/auth/linkaccount.go

@ -172,10 +172,10 @@ func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, r @@ -172,10 +172,10 @@ func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, r
log.Error("Error storing session: %v", err)
}
// If U2F is enrolled -> Redirect to U2F instead
regs, err := auth.GetU2FRegistrationsByUID(u.ID)
// If WebAuthn is enrolled -> Redirect to WebAuthn instead
regs, err := auth.GetWebAuthnCredentialsByUID(u.ID)
if err == nil && len(regs) > 0 {
ctx.Redirect(setting.AppSubURL + "/user/u2f")
ctx.Redirect(setting.AppSubURL + "/user/webauthn")
return
}

20
routers/web/auth/oauth.go

@ -34,7 +34,7 @@ import ( @@ -34,7 +34,7 @@ import (
user_service "code.gitea.io/gitea/services/user"
"gitea.com/go-chi/binding"
"github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v4"
"github.com/markbates/goth"
)
@ -149,7 +149,8 @@ func newAccessTokenResponse(grant *auth.OAuth2Grant, serverKey, clientKey oauth2 @@ -149,7 +149,8 @@ func newAccessTokenResponse(grant *auth.OAuth2Grant, serverKey, clientKey oauth2
accessToken := &oauth2.Token{
GrantID: grant.ID,
Type: oauth2.TypeAccessToken,
StandardClaims: jwt.StandardClaims{
// FIXME: Migrate to RegisteredClaims
StandardClaims: jwt.StandardClaims{ //nolint
ExpiresAt: expirationDate.AsTime().Unix(),
},
}
@ -167,7 +168,8 @@ func newAccessTokenResponse(grant *auth.OAuth2Grant, serverKey, clientKey oauth2 @@ -167,7 +168,8 @@ func newAccessTokenResponse(grant *auth.OAuth2Grant, serverKey, clientKey oauth2
GrantID: grant.ID,
Counter: grant.Counter,
Type: oauth2.TypeRefreshToken,
StandardClaims: jwt.StandardClaims{
// FIXME: Migrate to RegisteredClaims
StandardClaims: jwt.StandardClaims{ // nolint
ExpiresAt: refreshExpirationDate,
},
}
@ -205,7 +207,8 @@ func newAccessTokenResponse(grant *auth.OAuth2Grant, serverKey, clientKey oauth2 @@ -205,7 +207,8 @@ func newAccessTokenResponse(grant *auth.OAuth2Grant, serverKey, clientKey oauth2
}
idToken := &oauth2.OIDCToken{
StandardClaims: jwt.StandardClaims{
// FIXME: migrate to RegisteredClaims
StandardClaims: jwt.StandardClaims{ //nolint
ExpiresAt: expirationDate.AsTime().Unix(),
Issuer: setting.AppURL,
Audience: app.ClientID,
@ -326,7 +329,8 @@ func IntrospectOAuth(ctx *context.Context) { @@ -326,7 +329,8 @@ func IntrospectOAuth(ctx *context.Context) {
var response struct {
Active bool `json:"active"`
Scope string `json:"scope,omitempty"`
jwt.StandardClaims
// FIXME: Migrate to RegisteredClaims
jwt.StandardClaims //nolint
}
form := web.GetForm(ctx).(*forms.IntrospectTokenForm)
@ -1066,10 +1070,10 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model @@ -1066,10 +1070,10 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
log.Error("Error storing session: %v", err)
}
// If U2F is enrolled -> Redirect to U2F instead
regs, err := auth.GetU2FRegistrationsByUID(u.ID)
// If WebAuthn is enrolled -> Redirect to WebAuthn instead
regs, err := auth.GetWebAuthnCredentialsByUID(u.ID)
if err == nil && len(regs) > 0 {
ctx.Redirect(setting.AppSubURL + "/user/u2f")
ctx.Redirect(setting.AppSubURL + "/user/webauthn")
return
}

2
routers/web/auth/oauth_test.go

@ -12,7 +12,7 @@ import ( @@ -12,7 +12,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/services/auth/source/oauth2"
"github.com/golang-jwt/jwt"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/assert"
)

136
routers/web/auth/u2f.go

@ -1,136 +0,0 @@ @@ -1,136 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"errors"
"net/http"
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/externalaccount"
"github.com/tstranex/u2f"
)
var tplU2F base.TplName = "user/auth/u2f"
// U2F shows the U2F login page
func U2F(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("twofa")
ctx.Data["RequireU2F"] = true
// Check auto-login.
if checkAutoLogin(ctx) {