Refactor to use OpenID Connect OmniAuth gem instead of plain OAuth2
Right, I didn't totally connect the dots that there's some OpenID features in the mix here for how we expect to identify the user once they authenticate. It requires looking up the provider's public key, and validating the JWT they sent us. This gem does all that for us! I don't actually know what a real NeoPass `id_token` looks like yet? But I'll fill in some placeholder stuff for now, and use that for initializing the account!
This commit is contained in:
parent
ffcfce2eb8
commit
9cbeee0acd
26 changed files with 89 additions and 88 deletions
2
Gemfile
2
Gemfile
|
@ -27,7 +27,7 @@ gem 'devise', '~> 4.9', '>= 4.9.2'
|
|||
gem 'devise-encryptable', '~> 0.2.0'
|
||||
gem 'omniauth', '~> 2.1'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 1.0'
|
||||
gem 'omniauth-oauth2', '~> 1.8'
|
||||
gem "omniauth_openid_connect", "~> 0.7.1"
|
||||
|
||||
# For pagination UI.
|
||||
gem 'will_paginate', '~> 4.0'
|
||||
|
|
65
Gemfile.lock
65
Gemfile.lock
|
@ -83,6 +83,7 @@ GEM
|
|||
tzinfo (~> 2.0)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
async (2.8.1)
|
||||
console (~> 1.10)
|
||||
fiber-annotation
|
||||
|
@ -105,6 +106,7 @@ GEM
|
|||
async
|
||||
async-pool (0.4.0)
|
||||
async (>= 1.25)
|
||||
attr_required (1.0.2)
|
||||
babel-source (5.8.35)
|
||||
babel-transpiler (0.7.0)
|
||||
babel-source (>= 4.0, < 6)
|
||||
|
@ -112,6 +114,7 @@ GEM
|
|||
base64 (0.2.0)
|
||||
bcrypt (3.1.20)
|
||||
bigdecimal (3.1.6)
|
||||
bindata (2.5.0)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.3)
|
||||
msgpack (~> 1.2)
|
||||
|
@ -140,6 +143,8 @@ GEM
|
|||
drb (2.2.0)
|
||||
ruby2_keywords
|
||||
e2mmap (0.1.0)
|
||||
email_validator (2.2.4)
|
||||
activemodel
|
||||
erubi (1.12.0)
|
||||
execjs (2.9.1)
|
||||
falcon (0.43.0)
|
||||
|
@ -157,6 +162,8 @@ GEM
|
|||
samovar (~> 2.1)
|
||||
faraday (2.9.0)
|
||||
faraday-net_http (>= 2.0, < 3.2)
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-net_http (3.1.0)
|
||||
net-http
|
||||
ffi (1.16.3)
|
||||
|
@ -183,8 +190,13 @@ GEM
|
|||
jsbundling-rails (1.3.0)
|
||||
railties (>= 6.0.0)
|
||||
json (2.7.1)
|
||||
jwt (2.8.1)
|
||||
json-jwt (1.16.6)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
base64
|
||||
bindata
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
launchy (2.5.2)
|
||||
addressable (~> 2.8)
|
||||
letter_opener (1.9.0)
|
||||
|
@ -223,23 +235,29 @@ GEM
|
|||
nokogiri (1.16.2)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
oauth2 (2.0.9)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 4)
|
||||
snaky_hash (~> 2.0)
|
||||
version_gem (~> 1.1)
|
||||
omniauth (2.1.2)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 2.2.3)
|
||||
rack-protection
|
||||
omniauth-oauth2 (1.8.0)
|
||||
oauth2 (>= 1.4, < 3)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-rails_csrf_protection (1.0.1)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (~> 2.0)
|
||||
omniauth_openid_connect (0.7.1)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 2.2)
|
||||
openid_connect (2.3.0)
|
||||
activemodel
|
||||
attr_required (>= 1.0.0)
|
||||
email_validator
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.16)
|
||||
mail
|
||||
rack-oauth2 (~> 2.2)
|
||||
swd (~> 2.0)
|
||||
tzinfo
|
||||
validate_url
|
||||
webfinger (~> 2.0)
|
||||
openssl (3.2.0)
|
||||
orm_adapter (0.5.0)
|
||||
parallel (1.24.0)
|
||||
|
@ -265,6 +283,13 @@ GEM
|
|||
rack (>= 1.0, < 4)
|
||||
rack-mini-profiler (3.3.1)
|
||||
rack (>= 1.2.0)
|
||||
rack-oauth2 (2.2.1)
|
||||
activesupport
|
||||
attr_required
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-protection (4.0.0)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
|
@ -349,9 +374,6 @@ GEM
|
|||
shell (0.8.1)
|
||||
e2mmap
|
||||
sync
|
||||
snaky_hash (2.0.1)
|
||||
hashie
|
||||
version_gem (~> 1.1, >= 1.1.1)
|
||||
sprockets (4.2.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (>= 2.2.4, < 4)
|
||||
|
@ -363,6 +385,11 @@ GEM
|
|||
mini_portile2 (~> 2.8.0)
|
||||
stackprof (0.2.26)
|
||||
stringio (3.1.0)
|
||||
swd (2.0.3)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
sync (0.5.0)
|
||||
temple (0.10.3)
|
||||
terser (1.2.0)
|
||||
|
@ -380,7 +407,9 @@ GEM
|
|||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
uri (0.13.0)
|
||||
version_gem (1.1.3)
|
||||
validate_url (1.0.15)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.1)
|
||||
|
@ -388,6 +417,10 @@ GEM
|
|||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 6.0.0)
|
||||
webfinger (2.1.3)
|
||||
activesupport
|
||||
faraday (~> 2.0)
|
||||
faraday-follow_redirects
|
||||
webrick (1.8.1)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
|
@ -417,8 +450,8 @@ DEPENDENCIES
|
|||
mysql2 (~> 0.5.5)
|
||||
nokogiri (~> 1.15, >= 1.15.3)
|
||||
omniauth (~> 2.1)
|
||||
omniauth-oauth2 (~> 1.8)
|
||||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
omniauth_openid_connect (~> 0.7.1)
|
||||
parallel (~> 1.23)
|
||||
rack-attack (~> 6.7)
|
||||
rack-mini-profiler (~> 3.1)
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
* A test NeoPass server! This is a very lean, hacky implementation, designed
|
||||
* to just see the basic OAuth interactions Work At All.
|
||||
*
|
||||
* First, we have a "backing server", which is a `oauth2-mock-server` instance
|
||||
* that's easy to spin up and have perform OAuth for us. We give it a hardcoded
|
||||
* development-only key, and it just auto-grants permissions!
|
||||
* This server is an `oauth2-mock-server` instance that's easy to spin up and
|
||||
* have perform OAuth for us. We give it a hardcoded development-only key, and
|
||||
* it just auto-grants permissions!
|
||||
*
|
||||
* We also have a "main server", which obeys the actual NeoPass API: the
|
||||
* backing server isn't configurable with stuff like paths, so we use the main
|
||||
* server to proxy from the paths in the NeoPass spec to the paths the backing
|
||||
* server uses.
|
||||
* It slightly differs from the NeoPass spec, in that it uses different paths
|
||||
* for its endpoints, but that's okay: DTI will use OpenID's "discovery"
|
||||
* feature to discover those endpoints via a single well-known path, without
|
||||
* needing them hardcoded.
|
||||
*/
|
||||
|
||||
const fs = require("node:fs/promises");
|
||||
|
@ -67,7 +67,7 @@ async function ensureCertsExist() {
|
|||
}
|
||||
}
|
||||
|
||||
async function startBackingServer(port) {
|
||||
async function startServer(port) {
|
||||
const server = new OAuth2Server(
|
||||
keyPath,
|
||||
certPath,
|
||||
|
@ -90,53 +90,12 @@ async function startBackingServer(port) {
|
|||
});
|
||||
|
||||
await server.start(port, "localhost");
|
||||
console.log(`Started NeoPass backing server at: ${server.issuer.url}`);
|
||||
}
|
||||
|
||||
async function startMainServer(port) {
|
||||
const fetch = (await import("node-fetch")).default;
|
||||
|
||||
const app = express();
|
||||
app.use(express.text({ type: "*/*" }));
|
||||
|
||||
app.get("/", (req, res) => res.end("NeoPass development server for DTI!"));
|
||||
|
||||
app.get("/oauth2/auth", (req, res) => {
|
||||
const query = urlLib.parse(req.url).query;
|
||||
res.redirect(`http://localhost:8686/authorize?${query}`);
|
||||
});
|
||||
|
||||
app.post("/oauth2/token", async (req, res) => {
|
||||
try {
|
||||
// For POST requests, the HTTP spec doesn't allow a redirect to a
|
||||
// POST, so we proxy the request instead.
|
||||
const backingRes = await fetch("http://localhost:8686/token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": req.get("Content-Type"),
|
||||
},
|
||||
body: req.body,
|
||||
});
|
||||
if (!backingRes.ok) {
|
||||
throw new Error(`backing server returned status ${res.status}`);
|
||||
}
|
||||
|
||||
res.set("Content-Type", backingRes.headers.get("Content-Type"));
|
||||
return res.end(await backingRes.text());
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return res.end(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise((resolve) => app.listen(port, resolve));
|
||||
console.log(`Started NeoPass main server at: http://localhost:${port}`);
|
||||
console.log(`Started NeoPass development server at: ${server.issuer.url}`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await ensureCertsExist();
|
||||
await startBackingServer(8686);
|
||||
await startMainServer(8585);
|
||||
await startServer(8585);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
|
|
|
@ -121,5 +121,9 @@ Rails.application.configure do
|
|||
config.neopass_access_secret = "1"
|
||||
|
||||
# Use the local NeoPass development server.
|
||||
config.neopass_origin = "http://localhost:8585"
|
||||
config.neopass_origin = "https://localhost:8585"
|
||||
|
||||
# Set the NeoPass redirect callback URL.
|
||||
config.neopass_redirect_uri =
|
||||
"http://localhost:3000/auth_users/auth/neopass/callback"
|
||||
end
|
||||
|
|
|
@ -140,4 +140,8 @@ Rails.application.configure do
|
|||
|
||||
# Use the live NeoPass production server.
|
||||
config.neopass_origin = "https://oidc.neopets.com"
|
||||
|
||||
# Set the NeoPass redirect callback URL.
|
||||
config.neopass_redirect_uri =
|
||||
"https://impress.openneo.net/auth_users/auth/neopass/callback"
|
||||
end
|
||||
|
|
|
@ -76,5 +76,9 @@ Rails.application.configure do
|
|||
config.neopass_access_secret = "1"
|
||||
|
||||
# Use the local NeoPass development server.
|
||||
config.neopass_origin = "http://localhost:8585"
|
||||
config.neopass_origin = "https://localhost:8585"
|
||||
|
||||
# Set the NeoPass redirect callback URL.
|
||||
config.neopass_redirect_uri =
|
||||
"http://localhost:3000/auth_users/auth/neopass/callback"
|
||||
end
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
require "strategies/neopass"
|
||||
|
||||
# Assuming you have not yet modified this file, each configuration option below
|
||||
# is set to its default value. Note that some are commented out while others
|
||||
|
@ -274,7 +273,21 @@ Devise.setup do |config|
|
|||
# ==> OmniAuth
|
||||
# Add a new OmniAuth provider. Check the wiki for more information on setting
|
||||
# up on your models and hooks.
|
||||
config.omniauth :neopass, strategy_class: Strategies::NeoPass
|
||||
config.omniauth :openid_connect, {
|
||||
name: :neopass,
|
||||
scope: [:openid, :email, :profile],
|
||||
response_type: :code,
|
||||
issuer: Rails.configuration.neopass_origin,
|
||||
discovery: true,
|
||||
client_options: {
|
||||
identifier: "DTI-TODO",
|
||||
secret: "DTI-TODO",
|
||||
redirect_uri: Rails.configuration.neopass_redirect_uri,
|
||||
},
|
||||
}
|
||||
|
||||
# Output OmniAuth debug info to the server logs in development
|
||||
OmniAuth.config.logger = Rails.logger if Rails.env.development?
|
||||
|
||||
# ==> Warden configuration
|
||||
# If you want to use other strategies, that are not supported by Devise, or
|
||||
|
|
|
@ -18,7 +18,4 @@
|
|||
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
# Teach Zeitwerk that `RocketAMF` is what to expect in `lib/rocketamf`.
|
||||
inflect.acronym "RocketAMF"
|
||||
|
||||
# Teach Zeitwerk that "NeoPass" is what to expect in `neopass.rb`.
|
||||
inflect.acronym "NeoPass"
|
||||
end
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
require "omniauth-oauth2"
|
||||
|
||||
module Strategies
|
||||
class NeoPass < OmniAuth::Strategies::OAuth2
|
||||
option :name, "neopass"
|
||||
|
||||
option :client_options, {
|
||||
site: Rails.configuration.neopass_origin,
|
||||
authorize_url: "/oauth2/auth",
|
||||
token_url: "/oauth2/token",
|
||||
}
|
||||
end
|
||||
end
|
BIN
vendor/cache/aes_key_wrap-1.1.0.gem
vendored
Normal file
BIN
vendor/cache/aes_key_wrap-1.1.0.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/attr_required-1.0.2.gem
vendored
Normal file
BIN
vendor/cache/attr_required-1.0.2.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/bindata-2.5.0.gem
vendored
Normal file
BIN
vendor/cache/bindata-2.5.0.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/email_validator-2.2.4.gem
vendored
Normal file
BIN
vendor/cache/email_validator-2.2.4.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/faraday-follow_redirects-0.3.0.gem
vendored
Normal file
BIN
vendor/cache/faraday-follow_redirects-0.3.0.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/json-jwt-1.16.6.gem
vendored
Normal file
BIN
vendor/cache/json-jwt-1.16.6.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/jwt-2.8.1.gem
vendored
BIN
vendor/cache/jwt-2.8.1.gem
vendored
Binary file not shown.
BIN
vendor/cache/oauth2-2.0.9.gem
vendored
BIN
vendor/cache/oauth2-2.0.9.gem
vendored
Binary file not shown.
BIN
vendor/cache/omniauth-oauth2-1.8.0.gem
vendored
BIN
vendor/cache/omniauth-oauth2-1.8.0.gem
vendored
Binary file not shown.
BIN
vendor/cache/omniauth_openid_connect-0.7.1.gem
vendored
Normal file
BIN
vendor/cache/omniauth_openid_connect-0.7.1.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/openid_connect-2.3.0.gem
vendored
Normal file
BIN
vendor/cache/openid_connect-2.3.0.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/rack-oauth2-2.2.1.gem
vendored
Normal file
BIN
vendor/cache/rack-oauth2-2.2.1.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/snaky_hash-2.0.1.gem
vendored
BIN
vendor/cache/snaky_hash-2.0.1.gem
vendored
Binary file not shown.
BIN
vendor/cache/swd-2.0.3.gem
vendored
Normal file
BIN
vendor/cache/swd-2.0.3.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/validate_url-1.0.15.gem
vendored
Normal file
BIN
vendor/cache/validate_url-1.0.15.gem
vendored
Normal file
Binary file not shown.
BIN
vendor/cache/version_gem-1.1.3.gem
vendored
BIN
vendor/cache/version_gem-1.1.3.gem
vendored
Binary file not shown.
BIN
vendor/cache/webfinger-2.1.3.gem
vendored
Normal file
BIN
vendor/cache/webfinger-2.1.3.gem
vendored
Normal file
Binary file not shown.
Loading…
Reference in a new issue