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:
Emi Matchu 2024-03-14 18:11:40 -07:00
parent ffcfce2eb8
commit 9cbeee0acd
26 changed files with 89 additions and 88 deletions

View file

@ -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'

View file

@ -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)

View file

@ -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) => {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

Binary file not shown.

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

Binary file not shown.

BIN
vendor/cache/email_validator-2.2.4.gem vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/json-jwt-1.16.6.gem vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

BIN
vendor/cache/webfinger-2.1.3.gem vendored Normal file

Binary file not shown.