omniauth-nft 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +23 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +221 -0
- data/README.md +66 -0
- data/Rakefile +12 -0
- data/app/controllers/omni_auth/nft/nft_controller.rb +57 -0
- data/app/views/omni_auth/nft/nft/request_phase.html.erb +152 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/omniauth/nft/rails.rb +8 -0
- data/lib/omniauth/nft/version.rb +7 -0
- data/lib/omniauth/strategies/nft.rb +93 -0
- data/lib/omniauth-nft.rb +8 -0
- data/omniauth-nft.gemspec +43 -0
- metadata +189 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fab767120631dccbb9a10a5bd2e6b4dd278d752dfd23d8d982c6901b3e0ffc1c
|
|
4
|
+
data.tar.gz: aae937ccbc46271453178650d8ef148755ca02b10fa93e4ddbeacfc3632cf97c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: da8b9ae41177ecb698515d0f814bf4f869e176e847120bb87802ac54ce4a9e78e1f2d2ae450749e30cb00137eecab1883cd06533e375b30e510b808aa298346a
|
|
7
|
+
data.tar.gz: 6bf3ae6e0193d14fbb06aef22f4e3b490bf0d2d82eadd03de7767c6744bccc4e7979ed6f1982a723049ff9b86b3a44dd6905fe05f3aca3871f066bdf634f315b
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 2.6
|
|
3
|
+
NewCops: enable
|
|
4
|
+
|
|
5
|
+
Style/StringLiterals:
|
|
6
|
+
Enabled: true
|
|
7
|
+
EnforcedStyle: double_quotes
|
|
8
|
+
|
|
9
|
+
Style/StringLiteralsInInterpolation:
|
|
10
|
+
Enabled: true
|
|
11
|
+
EnforcedStyle: double_quotes
|
|
12
|
+
|
|
13
|
+
Layout/LineLength:
|
|
14
|
+
Max: 120
|
|
15
|
+
|
|
16
|
+
Metrics/BlockLength:
|
|
17
|
+
Exclude:
|
|
18
|
+
- '*.gemspec'
|
|
19
|
+
- 'spec/**/*'
|
|
20
|
+
|
|
21
|
+
Naming/FileName:
|
|
22
|
+
Exclude:
|
|
23
|
+
- 'lib/omniauth-nft.rb'
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.0.3
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source "https://rubygems.org"
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in omniauth-nft.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
gem "eth", "~> 0.4.17"
|
|
9
|
+
gem "rails", "~> 7.0"
|
|
10
|
+
gem "rake", "~> 13.0"
|
|
11
|
+
gem "rspec", "~> 3.0"
|
|
12
|
+
gem "rubocop", "~> 1.21"
|
|
13
|
+
gem "rubocop-rails", "~> 2.13"
|
|
14
|
+
gem "rubocop-rake", "~> 0.6.0"
|
|
15
|
+
gem "rubocop-rspec", "~> 2.7"
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
omniauth-nft (0.1.0)
|
|
5
|
+
eth (~> 0.4)
|
|
6
|
+
nft_checker (~> 0.3)
|
|
7
|
+
omniauth (~> 2.0)
|
|
8
|
+
rails (>= 6.0.0)
|
|
9
|
+
|
|
10
|
+
GEM
|
|
11
|
+
remote: https://rubygems.org/
|
|
12
|
+
specs:
|
|
13
|
+
actioncable (7.0.0)
|
|
14
|
+
actionpack (= 7.0.0)
|
|
15
|
+
activesupport (= 7.0.0)
|
|
16
|
+
nio4r (~> 2.0)
|
|
17
|
+
websocket-driver (>= 0.6.1)
|
|
18
|
+
actionmailbox (7.0.0)
|
|
19
|
+
actionpack (= 7.0.0)
|
|
20
|
+
activejob (= 7.0.0)
|
|
21
|
+
activerecord (= 7.0.0)
|
|
22
|
+
activestorage (= 7.0.0)
|
|
23
|
+
activesupport (= 7.0.0)
|
|
24
|
+
mail (>= 2.7.1)
|
|
25
|
+
actionmailer (7.0.0)
|
|
26
|
+
actionpack (= 7.0.0)
|
|
27
|
+
actionview (= 7.0.0)
|
|
28
|
+
activejob (= 7.0.0)
|
|
29
|
+
activesupport (= 7.0.0)
|
|
30
|
+
mail (~> 2.5, >= 2.5.4)
|
|
31
|
+
rails-dom-testing (~> 2.0)
|
|
32
|
+
actionpack (7.0.0)
|
|
33
|
+
actionview (= 7.0.0)
|
|
34
|
+
activesupport (= 7.0.0)
|
|
35
|
+
rack (~> 2.0, >= 2.2.0)
|
|
36
|
+
rack-test (>= 0.6.3)
|
|
37
|
+
rails-dom-testing (~> 2.0)
|
|
38
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
|
39
|
+
actiontext (7.0.0)
|
|
40
|
+
actionpack (= 7.0.0)
|
|
41
|
+
activerecord (= 7.0.0)
|
|
42
|
+
activestorage (= 7.0.0)
|
|
43
|
+
activesupport (= 7.0.0)
|
|
44
|
+
globalid (>= 0.6.0)
|
|
45
|
+
nokogiri (>= 1.8.5)
|
|
46
|
+
actionview (7.0.0)
|
|
47
|
+
activesupport (= 7.0.0)
|
|
48
|
+
builder (~> 3.1)
|
|
49
|
+
erubi (~> 1.4)
|
|
50
|
+
rails-dom-testing (~> 2.0)
|
|
51
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
|
52
|
+
activejob (7.0.0)
|
|
53
|
+
activesupport (= 7.0.0)
|
|
54
|
+
globalid (>= 0.3.6)
|
|
55
|
+
activemodel (7.0.0)
|
|
56
|
+
activesupport (= 7.0.0)
|
|
57
|
+
activerecord (7.0.0)
|
|
58
|
+
activemodel (= 7.0.0)
|
|
59
|
+
activesupport (= 7.0.0)
|
|
60
|
+
activestorage (7.0.0)
|
|
61
|
+
actionpack (= 7.0.0)
|
|
62
|
+
activejob (= 7.0.0)
|
|
63
|
+
activerecord (= 7.0.0)
|
|
64
|
+
activesupport (= 7.0.0)
|
|
65
|
+
marcel (~> 1.0)
|
|
66
|
+
mini_mime (>= 1.1.0)
|
|
67
|
+
activesupport (7.0.0)
|
|
68
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
69
|
+
i18n (>= 1.6, < 2)
|
|
70
|
+
minitest (>= 5.1)
|
|
71
|
+
tzinfo (~> 2.0)
|
|
72
|
+
ast (2.4.2)
|
|
73
|
+
builder (3.2.4)
|
|
74
|
+
concurrent-ruby (1.1.9)
|
|
75
|
+
crass (1.0.6)
|
|
76
|
+
diff-lcs (1.5.0)
|
|
77
|
+
erubi (1.10.0)
|
|
78
|
+
eth (0.4.17)
|
|
79
|
+
ffi (~> 1.15)
|
|
80
|
+
keccak (~> 1.3)
|
|
81
|
+
money-tree (~> 0.10)
|
|
82
|
+
rlp (~> 0.7)
|
|
83
|
+
scrypt (~> 3.0)
|
|
84
|
+
ffi (1.15.4)
|
|
85
|
+
ffi-compiler (1.0.1)
|
|
86
|
+
ffi (>= 1.0.0)
|
|
87
|
+
rake
|
|
88
|
+
globalid (1.0.0)
|
|
89
|
+
activesupport (>= 5.0)
|
|
90
|
+
hashie (5.0.0)
|
|
91
|
+
httparty (0.20.0)
|
|
92
|
+
mime-types (~> 3.0)
|
|
93
|
+
multi_xml (>= 0.5.2)
|
|
94
|
+
i18n (1.8.11)
|
|
95
|
+
concurrent-ruby (~> 1.0)
|
|
96
|
+
keccak (1.3.0)
|
|
97
|
+
loofah (2.13.0)
|
|
98
|
+
crass (~> 1.0.2)
|
|
99
|
+
nokogiri (>= 1.5.9)
|
|
100
|
+
mail (2.7.1)
|
|
101
|
+
mini_mime (>= 0.1.1)
|
|
102
|
+
marcel (1.0.2)
|
|
103
|
+
method_source (1.0.0)
|
|
104
|
+
mime-types (3.4.1)
|
|
105
|
+
mime-types-data (~> 3.2015)
|
|
106
|
+
mime-types-data (3.2021.1115)
|
|
107
|
+
mini_mime (1.1.2)
|
|
108
|
+
minitest (5.15.0)
|
|
109
|
+
money-tree (0.10.0)
|
|
110
|
+
ffi
|
|
111
|
+
multi_xml (0.6.0)
|
|
112
|
+
nft_checker (0.3.0)
|
|
113
|
+
httparty (~> 0.20)
|
|
114
|
+
nio4r (2.5.8)
|
|
115
|
+
nokogiri (1.12.5-x86_64-darwin)
|
|
116
|
+
racc (~> 1.4)
|
|
117
|
+
omniauth (2.0.4)
|
|
118
|
+
hashie (>= 3.4.6)
|
|
119
|
+
rack (>= 1.6.2, < 3)
|
|
120
|
+
rack-protection
|
|
121
|
+
parallel (1.21.0)
|
|
122
|
+
parser (3.0.3.2)
|
|
123
|
+
ast (~> 2.4.1)
|
|
124
|
+
racc (1.6.0)
|
|
125
|
+
rack (2.2.3)
|
|
126
|
+
rack-protection (2.1.0)
|
|
127
|
+
rack
|
|
128
|
+
rack-test (1.1.0)
|
|
129
|
+
rack (>= 1.0, < 3)
|
|
130
|
+
rails (7.0.0)
|
|
131
|
+
actioncable (= 7.0.0)
|
|
132
|
+
actionmailbox (= 7.0.0)
|
|
133
|
+
actionmailer (= 7.0.0)
|
|
134
|
+
actionpack (= 7.0.0)
|
|
135
|
+
actiontext (= 7.0.0)
|
|
136
|
+
actionview (= 7.0.0)
|
|
137
|
+
activejob (= 7.0.0)
|
|
138
|
+
activemodel (= 7.0.0)
|
|
139
|
+
activerecord (= 7.0.0)
|
|
140
|
+
activestorage (= 7.0.0)
|
|
141
|
+
activesupport (= 7.0.0)
|
|
142
|
+
bundler (>= 1.15.0)
|
|
143
|
+
railties (= 7.0.0)
|
|
144
|
+
rails-dom-testing (2.0.3)
|
|
145
|
+
activesupport (>= 4.2.0)
|
|
146
|
+
nokogiri (>= 1.6)
|
|
147
|
+
rails-html-sanitizer (1.4.2)
|
|
148
|
+
loofah (~> 2.3)
|
|
149
|
+
railties (7.0.0)
|
|
150
|
+
actionpack (= 7.0.0)
|
|
151
|
+
activesupport (= 7.0.0)
|
|
152
|
+
method_source
|
|
153
|
+
rake (>= 12.2)
|
|
154
|
+
thor (~> 1.0)
|
|
155
|
+
zeitwerk (~> 2.5)
|
|
156
|
+
rainbow (3.0.0)
|
|
157
|
+
rake (13.0.6)
|
|
158
|
+
regexp_parser (2.2.0)
|
|
159
|
+
rexml (3.2.5)
|
|
160
|
+
rlp (0.7.3)
|
|
161
|
+
rspec (3.10.0)
|
|
162
|
+
rspec-core (~> 3.10.0)
|
|
163
|
+
rspec-expectations (~> 3.10.0)
|
|
164
|
+
rspec-mocks (~> 3.10.0)
|
|
165
|
+
rspec-core (3.10.1)
|
|
166
|
+
rspec-support (~> 3.10.0)
|
|
167
|
+
rspec-expectations (3.10.1)
|
|
168
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
169
|
+
rspec-support (~> 3.10.0)
|
|
170
|
+
rspec-mocks (3.10.2)
|
|
171
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
172
|
+
rspec-support (~> 3.10.0)
|
|
173
|
+
rspec-support (3.10.3)
|
|
174
|
+
rubocop (1.24.1)
|
|
175
|
+
parallel (~> 1.10)
|
|
176
|
+
parser (>= 3.0.0.0)
|
|
177
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
178
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
179
|
+
rexml
|
|
180
|
+
rubocop-ast (>= 1.15.1, < 2.0)
|
|
181
|
+
ruby-progressbar (~> 1.7)
|
|
182
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
|
183
|
+
rubocop-ast (1.15.1)
|
|
184
|
+
parser (>= 3.0.1.1)
|
|
185
|
+
rubocop-rails (2.13.0)
|
|
186
|
+
activesupport (>= 4.2.0)
|
|
187
|
+
rack (>= 1.1)
|
|
188
|
+
rubocop (>= 1.7.0, < 2.0)
|
|
189
|
+
rubocop-rake (0.6.0)
|
|
190
|
+
rubocop (~> 1.0)
|
|
191
|
+
rubocop-rspec (2.7.0)
|
|
192
|
+
rubocop (~> 1.19)
|
|
193
|
+
ruby-progressbar (1.11.0)
|
|
194
|
+
scrypt (3.0.7)
|
|
195
|
+
ffi-compiler (>= 1.0, < 2.0)
|
|
196
|
+
thor (1.1.0)
|
|
197
|
+
tzinfo (2.0.4)
|
|
198
|
+
concurrent-ruby (~> 1.0)
|
|
199
|
+
unicode-display_width (2.1.0)
|
|
200
|
+
websocket-driver (0.7.5)
|
|
201
|
+
websocket-extensions (>= 0.1.0)
|
|
202
|
+
websocket-extensions (0.1.5)
|
|
203
|
+
zeitwerk (2.5.3)
|
|
204
|
+
|
|
205
|
+
PLATFORMS
|
|
206
|
+
x86_64-darwin-19
|
|
207
|
+
x86_64-darwin-21
|
|
208
|
+
|
|
209
|
+
DEPENDENCIES
|
|
210
|
+
eth (~> 0.4.17)
|
|
211
|
+
omniauth-nft!
|
|
212
|
+
rails (~> 7.0)
|
|
213
|
+
rake (~> 13.0)
|
|
214
|
+
rspec (~> 3.0)
|
|
215
|
+
rubocop (~> 1.21)
|
|
216
|
+
rubocop-rails (~> 2.13)
|
|
217
|
+
rubocop-rake (~> 0.6.0)
|
|
218
|
+
rubocop-rspec (~> 2.7)
|
|
219
|
+
|
|
220
|
+
BUNDLED WITH
|
|
221
|
+
2.2.32
|
data/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# OmniAuth::Nft
|
|
2
|
+
|
|
3
|
+
OmniAuth strategy for authenticating via NFT ownership
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'omniauth-nft'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
$ bundle install
|
|
16
|
+
|
|
17
|
+
Or install it yourself as:
|
|
18
|
+
|
|
19
|
+
$ gem install omniauth-nft
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Add as a strategy to your omniauth config. Eg:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
|
27
|
+
provider :nft,
|
|
28
|
+
checker_type: :opensea,
|
|
29
|
+
checker_options: { testnet: true },
|
|
30
|
+
nft_collection: { slug: 'untitled-collection-4919696' }
|
|
31
|
+
provider :developer unless Rails.env.production?
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
This configuration will run against OpenSea testnet and allow login for anyone that owns an NFT in the
|
|
35
|
+
[Test Prota Collection](https://testnets.opensea.io/collection/untitled-collection-4919696)
|
|
36
|
+
|
|
37
|
+
UID is set to a combination of nft_collection slug and the nft token_id.
|
|
38
|
+
Authenticated users will have an auth.info hash with the name and image url of the NFT.
|
|
39
|
+
`auth.extra` contains a `wallet` key (the authenticated wallet address) and a `raw_info`
|
|
40
|
+
hash which contains all NFT metadata retrieved by `NftChecker`.
|
|
41
|
+
|
|
42
|
+
You can implement `User#find_or_create_from_auth_hash` like this:
|
|
43
|
+
```ruby
|
|
44
|
+
def User.find_or_create_from_auth_hash(auth)
|
|
45
|
+
identity = { provider: auth['provider'], uid: auth['uid'] }
|
|
46
|
+
User.find_or_create_by!(identity) do |record|
|
|
47
|
+
case auth[:provider]
|
|
48
|
+
when 'nft'
|
|
49
|
+
record.name = auth.info['name']
|
|
50
|
+
record.profile_url = auth.info['image']
|
|
51
|
+
else
|
|
52
|
+
raise 'Unexpected provider!'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Development
|
|
59
|
+
|
|
60
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
61
|
+
|
|
62
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
63
|
+
|
|
64
|
+
## Contributing
|
|
65
|
+
|
|
66
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/valthon/omniauth-nft.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "eth"
|
|
4
|
+
module OmniAuth
|
|
5
|
+
module Nft
|
|
6
|
+
# Controller for OmniAuth::Strategies::Nft request phase.
|
|
7
|
+
class NftController < ActionController::Base
|
|
8
|
+
WALLET_SIG_SESSION_KEY = "nft__wallet_sig"
|
|
9
|
+
SIG_MESSAGE_SESSION_KEY = "nft__sig_message"
|
|
10
|
+
|
|
11
|
+
def request_phase
|
|
12
|
+
return unless address.present?
|
|
13
|
+
|
|
14
|
+
@address = address
|
|
15
|
+
return unless address_signed?
|
|
16
|
+
|
|
17
|
+
@sig = sig
|
|
18
|
+
checker = NftChecker.init(:opensea, testnet: true)
|
|
19
|
+
@list = checker.list_nfts({ slug: "untitled-collection-4919696" }, address)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def address
|
|
25
|
+
@address ||= begin
|
|
26
|
+
address = params.permit(:address)[:address].to_s
|
|
27
|
+
address if address =~ /\A0x[A-Za-z0-9]+\z/
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sig
|
|
32
|
+
@sig ||= begin
|
|
33
|
+
sig = params.permit(:sig)[:sig]
|
|
34
|
+
session[WALLET_SIG_SESSION_KEY] = sig if sig.present?
|
|
35
|
+
session[WALLET_SIG_SESSION_KEY]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def session_secret_message
|
|
40
|
+
session[SIG_MESSAGE_SESSION_KEY] ||= <<~SECRET
|
|
41
|
+
This is to verify you own the wallet with ID #{address}
|
|
42
|
+
|
|
43
|
+
| #{SecureRandom.uuid} |
|
|
44
|
+
SECRET
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def address_signed?
|
|
48
|
+
@msg = session_secret_message
|
|
49
|
+
recovered_public_key = Eth::Key.personal_recover(@msg, sig)
|
|
50
|
+
recovered_address = Eth::Utils.public_key_to_address(recovered_public_key)
|
|
51
|
+
@sig_valid = recovered_address.casecmp(address).zero?
|
|
52
|
+
rescue StandardError
|
|
53
|
+
@sig_valid = false
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
.metamask {
|
|
3
|
+
display: none;
|
|
4
|
+
}
|
|
5
|
+
.nft_request {
|
|
6
|
+
display: flex; flex-direction: column; text-align: center; width: 100%
|
|
7
|
+
}
|
|
8
|
+
.nft_request div {
|
|
9
|
+
margin: 16px;
|
|
10
|
+
}
|
|
11
|
+
button {
|
|
12
|
+
background-color: lightgreen;
|
|
13
|
+
padding: 2px 5px;
|
|
14
|
+
border: solid 1px black;
|
|
15
|
+
border-radius: 5px;
|
|
16
|
+
}
|
|
17
|
+
button:hover {
|
|
18
|
+
background-color: greenyellow;
|
|
19
|
+
}
|
|
20
|
+
button:active {
|
|
21
|
+
background-color: green;
|
|
22
|
+
color: white;
|
|
23
|
+
}
|
|
24
|
+
h2 {
|
|
25
|
+
display: block;
|
|
26
|
+
margin: 16px;
|
|
27
|
+
}
|
|
28
|
+
.nft_list {
|
|
29
|
+
margin: 10px;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: row;
|
|
32
|
+
align-content: space-evenly;
|
|
33
|
+
}
|
|
34
|
+
.thumbnail {
|
|
35
|
+
margin: 5px;
|
|
36
|
+
border: thin solid #666;
|
|
37
|
+
max-height: 128px;
|
|
38
|
+
max-width: 128px;
|
|
39
|
+
}
|
|
40
|
+
textarea {
|
|
41
|
+
width: 32em;
|
|
42
|
+
height: 6em;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
45
|
+
<script>
|
|
46
|
+
function set_address(id) {
|
|
47
|
+
document.querySelector('input[name=address]').value = id;
|
|
48
|
+
document.querySelector('form').submit();
|
|
49
|
+
}
|
|
50
|
+
function set_sig(sig) {
|
|
51
|
+
document.querySelector('textarea[name=sig]').value = sig;
|
|
52
|
+
document.querySelector('form').submit();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function checkEthereum(ethereum) {
|
|
56
|
+
if (typeof ethereum !== 'undefined') {
|
|
57
|
+
const accounts = await ethereum.request({ method: 'eth_accounts' });
|
|
58
|
+
if (accounts && accounts[ 0 ] && accounts[0] != '<%= @address %>') {
|
|
59
|
+
set_address(accounts[ 0 ])
|
|
60
|
+
} else {
|
|
61
|
+
document.querySelectorAll('.metamask').forEach(x => x.style.display = 'block');
|
|
62
|
+
document.querySelector('.metamask button').addEventListener('click', async x => {
|
|
63
|
+
x.preventDefault();
|
|
64
|
+
const accounts = await ethereum.request({ method: 'eth_requestAccounts' });
|
|
65
|
+
if (accounts && accounts[ 0 ]) {
|
|
66
|
+
set_address(accounts[ 0 ]);
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function tryEthereumSign(ethereum) {
|
|
74
|
+
if (typeof ethereum !== 'undefined') {
|
|
75
|
+
const accounts = await ethereum.request({ method: 'eth_requestAccounts' });
|
|
76
|
+
const oldAccount = '<%= @address %>';
|
|
77
|
+
if (accounts && accounts[ 0 ] && accounts[0] != '<%= @address %>') {
|
|
78
|
+
set_address(accounts[ 0 ])
|
|
79
|
+
} else if (accounts && accounts[0]) {
|
|
80
|
+
const account = accounts[0]
|
|
81
|
+
const message = document.querySelector('textarea[name=sigmsg]').value;
|
|
82
|
+
const sig = await ethereum.request({ method: 'personal_sign', params: [message, account] });
|
|
83
|
+
if (sig) {
|
|
84
|
+
set_sig(sig);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<% if @address.blank? %>
|
|
92
|
+
|
|
93
|
+
<div class="nft_request">
|
|
94
|
+
<div style="display:block">
|
|
95
|
+
Enter your wallet address:
|
|
96
|
+
<%= form_tag '', data: { turbo: false }, authenticity_token: params['authenticity_token'] do %>
|
|
97
|
+
<input class="address_input" name="address" type="text" placeholder="0x051c6..."/>
|
|
98
|
+
<button>Go!</button>
|
|
99
|
+
<% end %>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="metamask">
|
|
102
|
+
- or -
|
|
103
|
+
</div>
|
|
104
|
+
<div class="metamask">
|
|
105
|
+
Connect with <button>MetaMask</button>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<script>
|
|
110
|
+
checkEthereum(window.ethereum);
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<% elsif !@sig_valid %>
|
|
114
|
+
|
|
115
|
+
Please sign the following to continue...
|
|
116
|
+
|
|
117
|
+
<br/>
|
|
118
|
+
|
|
119
|
+
<textarea name=sigmsg disabled><%= @msg %></textarea>
|
|
120
|
+
|
|
121
|
+
<br/><br/>
|
|
122
|
+
|
|
123
|
+
<%= form_tag '', data: { turbo: false }, authenticity_token: params['authenticity_token'] do %>
|
|
124
|
+
<textarea placeholder="paste signature here..." name="sig"></textarea>
|
|
125
|
+
<%= hidden_field_tag :address, @address %>
|
|
126
|
+
<button>Go!</button>
|
|
127
|
+
<% end %>
|
|
128
|
+
|
|
129
|
+
<script>
|
|
130
|
+
tryEthereumSign(window.ethereum);
|
|
131
|
+
</script>
|
|
132
|
+
|
|
133
|
+
<%= link_to 'cancel', '/' %>
|
|
134
|
+
|
|
135
|
+
<script>
|
|
136
|
+
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<% elsif @list.blank? %>
|
|
140
|
+
<% else %>
|
|
141
|
+
<h2>Which NFT would you like to authenticate with?</h2>
|
|
142
|
+
<row>
|
|
143
|
+
<ul class="nft_list" >
|
|
144
|
+
<% @list.each do |nft| %>
|
|
145
|
+
<%= button_to create_session_path(provider: 'nft'), params: { address: @address, nft_id: nft['token_id'], nft_contract: nft['asset_contract']['address'], sig: @sig, msg: @msg } do %>
|
|
146
|
+
<%= image_tag nft['image_thumbnail_url'] || nft['image_url'], title: nft['name'], alt: nft['token_id'], class: 'thumbnail' %>
|
|
147
|
+
<% end %>
|
|
148
|
+
<% end %>
|
|
149
|
+
</ul>
|
|
150
|
+
</row>
|
|
151
|
+
|
|
152
|
+
<% end %>
|
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "omniauth-nft"
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require "irb"
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "omniauth"
|
|
4
|
+
require "nft_checker"
|
|
5
|
+
require "eth"
|
|
6
|
+
|
|
7
|
+
module OmniAuth
|
|
8
|
+
module Strategies
|
|
9
|
+
# An OmniAuth strategy for authenticating via NFT ownership
|
|
10
|
+
class Nft
|
|
11
|
+
include OmniAuth::Strategy
|
|
12
|
+
|
|
13
|
+
option :checker_type, :opensea
|
|
14
|
+
option :checker_options, {}
|
|
15
|
+
option :nft_collection, {}
|
|
16
|
+
# option :form, OmniAuth::Nft::NftController.action(:request_phase)
|
|
17
|
+
# option :form, true
|
|
18
|
+
|
|
19
|
+
def request_phase
|
|
20
|
+
OmniAuth::Nft::NftController.action(:request_phase).call(env)
|
|
21
|
+
end
|
|
22
|
+
# def request_phase
|
|
23
|
+
# checker = NftChecker.init(options.checker_type, options.checker_options)
|
|
24
|
+
# # checker.list_nfts({slug: 'untitled-collection-4919696'}, '0x051c6B791044102Ae773e27FEA21480ed6D653F4'
|
|
25
|
+
# raise 'party 2!'
|
|
26
|
+
#
|
|
27
|
+
# end
|
|
28
|
+
|
|
29
|
+
attr_reader :nft, :address
|
|
30
|
+
|
|
31
|
+
uid do
|
|
32
|
+
"#{nft["asset_contract"]["address"]}::#{nft["token_id"]}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
info do
|
|
36
|
+
{
|
|
37
|
+
"name" => nft["name"],
|
|
38
|
+
"image" => nft["image_url"]
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
extra do
|
|
43
|
+
{
|
|
44
|
+
"wallet" => address,
|
|
45
|
+
"raw_info" => nft
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def callback_phase # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
50
|
+
nft_params = ActionController::Parameters.new(request.params).permit(:address, :nft_id, :nft_contract, :sig)
|
|
51
|
+
return fail!(:no_wallet_address) if nft_params[:address].blank?
|
|
52
|
+
return fail!(:no_nft) if nft_params[:nft_id].blank? || nft_params[:nft_contract].blank?
|
|
53
|
+
|
|
54
|
+
nft_metadata = { contract_address: nft_params[:nft_contract], token_id: nft_params[:nft_id] }
|
|
55
|
+
nft = checker.fetch_nft_for_owner(nft_params[:address], nft_metadata)
|
|
56
|
+
return fail!(:invalid_nft) if nft.blank?
|
|
57
|
+
|
|
58
|
+
# At this point, we know the users owns the NFT, but not if it's part of our collection
|
|
59
|
+
return fail!(:not_in_collection) unless verify_collection(nft["collection"])
|
|
60
|
+
|
|
61
|
+
# Now we have verified the address owns the NFT and that the NFT is in our collection
|
|
62
|
+
return fail!(:invalid_signature) unless verify_signature(nft_params[:address], session["nft__sig_message"],
|
|
63
|
+
nft_params[:sig])
|
|
64
|
+
|
|
65
|
+
# And, completing the validation, we know the current user session owns the owning wallet
|
|
66
|
+
@address = nft_params[:address]
|
|
67
|
+
@nft = nft
|
|
68
|
+
super
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def checker
|
|
74
|
+
@checker = NftChecker.init(options.checker_type, options.checker_options)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def verify_signature(address, message, signature)
|
|
78
|
+
recovered_public_key = Eth::Key.personal_recover(message, signature)
|
|
79
|
+
recovered_address = Eth::Utils.public_key_to_address(recovered_public_key)
|
|
80
|
+
recovered_address.casecmp(address).zero?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def verify_collection(nft_collection)
|
|
84
|
+
return false if nft_collection.blank?
|
|
85
|
+
|
|
86
|
+
options.nft_collection.each_key do |key|
|
|
87
|
+
return false unless nft_collection[key.to_s].casecmp(options.nft_collection[key]).zero?
|
|
88
|
+
end
|
|
89
|
+
true
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
data/lib/omniauth-nft.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/omniauth/nft/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "omniauth-nft"
|
|
7
|
+
spec.version = OmniAuth::Nft::VERSION
|
|
8
|
+
spec.authors = ["David J Parrott"]
|
|
9
|
+
spec.email = ["valthon@nothlav.net"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "OmniAuth strategy for authenticating via NFT ownership"
|
|
12
|
+
spec.homepage = "https://github.com/valthon/omniauth-nft"
|
|
13
|
+
spec.required_ruby_version = ">= 2.6.0"
|
|
14
|
+
|
|
15
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/valthon/omniauth-nft"
|
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/valthon/omniauth-nft/blob/trunk/CHANGELOG.md"
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
spec.bindir = "exe"
|
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
30
|
+
spec.require_paths = ["lib"]
|
|
31
|
+
|
|
32
|
+
spec.add_dependency "eth", "~> 0.4"
|
|
33
|
+
spec.add_dependency "nft_checker", "~> 0.3"
|
|
34
|
+
spec.add_dependency "omniauth", "~> 2.0"
|
|
35
|
+
|
|
36
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
37
|
+
spec.add_development_dependency "rubocop", "~> 1.21"
|
|
38
|
+
spec.add_development_dependency "rubocop-rails", "~> 2.13"
|
|
39
|
+
spec.add_development_dependency "rubocop-rake", "~> 0.6"
|
|
40
|
+
spec.add_development_dependency "rubocop-rspec", "~> 0.6"
|
|
41
|
+
|
|
42
|
+
spec.add_runtime_dependency "rails", ">= 6.0.0"
|
|
43
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omniauth-nft
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- David J Parrott
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2022-01-01 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: eth
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.4'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.4'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: nft_checker
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.3'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.3'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: omniauth
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rubocop
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.21'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.21'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rubocop-rails
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '2.13'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '2.13'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rubocop-rake
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0.6'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0.6'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rubocop-rspec
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0.6'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '0.6'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rails
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: 6.0.0
|
|
132
|
+
type: :runtime
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: 6.0.0
|
|
139
|
+
description:
|
|
140
|
+
email:
|
|
141
|
+
- valthon@nothlav.net
|
|
142
|
+
executables: []
|
|
143
|
+
extensions: []
|
|
144
|
+
extra_rdoc_files: []
|
|
145
|
+
files:
|
|
146
|
+
- ".rspec"
|
|
147
|
+
- ".rubocop.yml"
|
|
148
|
+
- ".ruby-version"
|
|
149
|
+
- CHANGELOG.md
|
|
150
|
+
- Gemfile
|
|
151
|
+
- Gemfile.lock
|
|
152
|
+
- README.md
|
|
153
|
+
- Rakefile
|
|
154
|
+
- app/controllers/omni_auth/nft/nft_controller.rb
|
|
155
|
+
- app/views/omni_auth/nft/nft/request_phase.html.erb
|
|
156
|
+
- bin/console
|
|
157
|
+
- bin/setup
|
|
158
|
+
- lib/omniauth-nft.rb
|
|
159
|
+
- lib/omniauth/nft/rails.rb
|
|
160
|
+
- lib/omniauth/nft/version.rb
|
|
161
|
+
- lib/omniauth/strategies/nft.rb
|
|
162
|
+
- omniauth-nft.gemspec
|
|
163
|
+
homepage: https://github.com/valthon/omniauth-nft
|
|
164
|
+
licenses: []
|
|
165
|
+
metadata:
|
|
166
|
+
rubygems_mfa_required: 'true'
|
|
167
|
+
homepage_uri: https://github.com/valthon/omniauth-nft
|
|
168
|
+
source_code_uri: https://github.com/valthon/omniauth-nft
|
|
169
|
+
changelog_uri: https://github.com/valthon/omniauth-nft/blob/trunk/CHANGELOG.md
|
|
170
|
+
post_install_message:
|
|
171
|
+
rdoc_options: []
|
|
172
|
+
require_paths:
|
|
173
|
+
- lib
|
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
175
|
+
requirements:
|
|
176
|
+
- - ">="
|
|
177
|
+
- !ruby/object:Gem::Version
|
|
178
|
+
version: 2.6.0
|
|
179
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
|
+
requirements:
|
|
181
|
+
- - ">="
|
|
182
|
+
- !ruby/object:Gem::Version
|
|
183
|
+
version: '0'
|
|
184
|
+
requirements: []
|
|
185
|
+
rubygems_version: 3.2.32
|
|
186
|
+
signing_key:
|
|
187
|
+
specification_version: 4
|
|
188
|
+
summary: OmniAuth strategy for authenticating via NFT ownership
|
|
189
|
+
test_files: []
|