Emi Matchu
9cbeee0acd
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!
103 lines
4.1 KiB
JavaScript
Executable file
103 lines
4.1 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
/**
|
|
* A test NeoPass server! This is a very lean, hacky implementation, designed
|
|
* to just see the basic OAuth interactions Work At All.
|
|
*
|
|
* 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!
|
|
*
|
|
* 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");
|
|
const pathLib = require("node:path");
|
|
const { spawn } = require("node:child_process");
|
|
const urlLib = require("node:url");
|
|
|
|
const { OAuth2Server } = require("oauth2-mock-server");
|
|
const express = require("express");
|
|
|
|
const certPath = pathLib.join(__dirname, "..", "tmp", "localhost.pem");
|
|
const keyPath = pathLib.join(__dirname, "..", "tmp", "localhost-key.pem");
|
|
|
|
async function fileExists(path) {
|
|
try {
|
|
await fs.stat(path);
|
|
} catch (error) {
|
|
if (error.code === "ENOENT") {
|
|
return false;
|
|
}
|
|
throw error;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async function ensureCertsExist() {
|
|
if (!(await fileExists(certPath)) || !(await fileExists(keyPath))) {
|
|
console.log(
|
|
"Using mkcert to create localhost.pem and localhost-key.pem in " +
|
|
"the Rails tmp dir, to serve over HTTPS.",
|
|
);
|
|
|
|
const mkcertProc = spawn("mkcert", [
|
|
"-cert-file",
|
|
certPath,
|
|
"-key-file",
|
|
keyPath,
|
|
"localhost",
|
|
], {stdio: ["ignore", process.stdout, process.stderr]});
|
|
|
|
// Wait for the process to finish, raising an error if it fails.
|
|
await new Promise((resolve, reject) => {
|
|
mkcertProc.on("close", (code) => {
|
|
if (code === 0) {
|
|
resolve();
|
|
} else {
|
|
reject(new Error(`mkcert returned status ${code}`));
|
|
}
|
|
});
|
|
mkcertProc.on("error", (error) => {
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
async function startServer(port) {
|
|
const server = new OAuth2Server(
|
|
keyPath,
|
|
certPath,
|
|
);
|
|
await server.issuer.keys.add({
|
|
// A key we generated for the NeoPass test server. It's okay for its
|
|
// "secret" info to be here, because it's for development only!
|
|
kid: "neopass-server-DEVLOPMENT-ONLY-NOT-FOR-PRODUCTION",
|
|
p: "50btwsJlPbGLUFnCSBZzddyMX_oRQ8nz4lMrpAd4umPLqMUmS0NbBZNf6DI7s8PkRUxtV8KdvZh3OYWavFnbk55GDG4Y_J_wA4XlHU3d5KIfSNaIdbtVp4CFOq1lovho4sYX_26vcGgYb2Azeg7nz_gDpqNmIdJdKuZxzsrboK8",
|
|
kty: "RSA",
|
|
q: "xha0i9_lbOMQhmmni02Dtpocil26GI7W8xbOFyOvceBCRNf-XOA_-W_Xk9ItJRHnAWM1TML36PN0l864d4QAXbBo64FHu2cjdFKnXJNliJaPcOPAMQB_D8GSylU1gTwSpP_vVe8t232LeF1oBwbOoBIS-6NsOpLmL8Sezv6Fpac",
|
|
d: "WSUNeEd_EyaELK7wqT6GJJK_RfYjaE5h6USe9rD9cd_tQE2PaZWXMyZ4OCk5Z5hdG2ryZY7NYsOI2CPs8HCFBqMoKd0z0A0EgB8Dq2fe_-t5Rm0Zq1ZnI5tnBcZeQmw0hDT98Wg00FA53SSUqfnOgI_VuLvquM6f18_XQOKRRfTcwh1a4teDAH0g0s8FVOS5DANtg71mTdq5fEkWmQMD3qKC6SNrx3WXXHezDs0MWdeFqn9Dg7gssSqB7PnqB-hlC_fHnu4gm9nDqPTMzsJC2i8d3adm0AeORRCulGLe7hU-_TgTbZzgIYCgOK_asaewW-6Qk9qFj-J4djBaKIee-Q",
|
|
e: "AQAB",
|
|
use: "sig",
|
|
qi: "WNiwCcAk2x7e0KvuupL2DNU-JUjLEF9Onee5T9u9ihbgGSDIyP04_96TzCIK3wsY6lct64oOo0Er-z5cf_5eOBPD3n0eEL-JuKIgn0mEKrazJOnGQzlyeZPzk4dUO2J7D42ObopfYsoBIcJx-Y_43a6WORDMGSVCiURmKavTHUU",
|
|
dp: "p1_wj-Npq3VDElpzPQJqeuCrAoaSWhHcm21_hs0VdSbl6_UJ2qwbQnS-kudPx7A8El7WPw4MZHrjxdBIBImvXCzOGw7OrHz_ET2ka0nADUe7BlakGTgDLB7ZzHZSuNe36G5eTbCH7PyYunnPp0UERMEDu2RDdLSuUm7F7FdpDOc",
|
|
alg: "RS256",
|
|
dq: "purLCHKKKM7NRfYRsFiI_H2wPwfroHX8uqokz2rKk_Kc5NX9CNYOEmokBfO9BtenCIxIhX5k2G8NeD5BQrSAenIEdy5g-5FVVtevH1s023vDMyU29hOs_eHnh4d1poiwTUk8q_T3d1S7CZnr5r_drRSN2m1C7biLLwVHrLTceVE",
|
|
n: "svVfGU4NGcfBCmQiIOW5uzg5SAN2CWSIQSstnhqZoCdjy5OoKpKVR8O9TbDvxixrvkFyAav90Q0Xse8iFTcjfCKuqINYiuYMXhCvfBlc_DVVOQca9pMpN03LaDofd5Ll4_BFTtt1nSPahwWU7xDM-Bkkh_TcS2qS4N2xbpEGi0q0ZkrJN4WyiDBC2k9WbK-YHr4Rj4JKypFVSeBIrjxVPmlPzgfqlLGGIB0l92SnJDXDMlkWcCCTyLgqSBM04nkxGDSykq_ei76qCdRd7b10wMBaoS9DeBThAyHpur2LoPdH3gxbcwoWExi-jPlNP1LdKVZD8b95OY3CRyMAAMGdKQ",
|
|
});
|
|
|
|
await server.start(port, "localhost");
|
|
console.log(`Started NeoPass development server at: ${server.issuer.url}`);
|
|
}
|
|
|
|
async function main() {
|
|
await ensureCertsExist();
|
|
await startServer(8585);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
});
|