switch authenticated health checks to api keys

This commit is contained in:
Karol Wypchlo 2022-02-15 15:44:45 +01:00
parent 8f90385f3f
commit 1faa5c3319
No known key found for this signature in database
GPG Key ID: B515DE9EEBE241E1
5 changed files with 24 additions and 88 deletions

View File

@ -1,5 +1,5 @@
more_set_headers 'Access-Control-Allow-Origin: $http_origin'; more_set_headers 'Access-Control-Allow-Origin: $http_origin';
more_set_headers 'Access-Control-Allow-Credentials: true'; more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE'; more_set_headers 'Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE';
more_set_headers 'Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,If-None-Match,Cache-Control,Content-Type,Range,X-HTTP-Method-Override,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location,Skynet-API-Key'; more_set_headers 'Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,If-None-Match,Cache-Control,Content-Type,Range,X-HTTP-Method-Override,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location,Skynet-Api-Key';
more_set_headers 'Access-Control-Expose-Headers: Content-Length,Content-Range,ETag,Skynet-File-Metadata,Skynet-Skylink,Skynet-Proof,Skynet-Portal-Api,Skynet-Server-Api,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location'; more_set_headers 'Access-Control-Expose-Headers: Content-Length,Content-Range,ETag,Skynet-File-Metadata,Skynet-Skylink,Skynet-Proof,Skynet-Portal-Api,Skynet-Server-Api,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location';

View File

@ -6,16 +6,6 @@ require("yargs/yargs")(process.argv.slice(2))
.help() .help()
.demandCommand() .demandCommand()
.strict(true) .strict(true)
.command(
"__authenticate", // Internal only function - this function will be removed when API keys are implemented
false, // hide this function cli help
() => {},
async () => {
const { getAuthCookie } = require("../src/utils");
console.log(await getAuthCookie(true));
}
)
.command( .command(
"enable", "enable",
"Mark portal as enabled", "Mark portal as enabled",

View File

@ -1,12 +1,14 @@
const got = require("got"); const got = require("got");
const FormData = require("form-data"); const FormData = require("form-data");
const { isEqual } = require("lodash"); const { isEqual } = require("lodash");
const { calculateElapsedTime, getResponseContent, getAuthCookie, isPortalModuleEnabled } = require("../utils"); const { calculateElapsedTime, getResponseContent, isPortalModuleEnabled } = require("../utils");
const { SkynetClient, stringToUint8ArrayUtf8, genKeyPairAndSeed } = require("skynet-js"); const { SkynetClient, stringToUint8ArrayUtf8, genKeyPairAndSeed } = require("skynet-js");
const MODULE_BLOCKER = "b"; const MODULE_BLOCKER = "b";
const skynetClient = new SkynetClient(process.env.SKYNET_PORTAL_API); const skynetClient = new SkynetClient(process.env.SKYNET_PORTAL_API, {
skynetApiKey: process.env.ACCOUNTS_TEST_USER_API_KEY,
});
const exampleSkylink = "AACogzrAimYPG42tDOKhS3lXZD8YvlF8Q8R17afe95iV2Q"; const exampleSkylink = "AACogzrAimYPG42tDOKhS3lXZD8YvlF8Q8R17afe95iV2Q";
// check that any relevant configuration is properly set in skyd // check that any relevant configuration is properly set in skyd
@ -36,7 +38,6 @@ async function skydConfigCheck(done) {
// uploadCheck returns the result of uploading a sample file // uploadCheck returns the result of uploading a sample file
async function uploadCheck(done) { async function uploadCheck(done) {
const authCookie = await getAuthCookie();
const time = process.hrtime(); const time = process.hrtime();
const form = new FormData(); const form = new FormData();
const payload = Buffer.from(new Date()); // current date to ensure data uniqueness const payload = Buffer.from(new Date()); // current date to ensure data uniqueness
@ -47,7 +48,9 @@ async function uploadCheck(done) {
try { try {
const response = await got.post(`${process.env.SKYNET_PORTAL_API}/skynet/skyfile`, { const response = await got.post(`${process.env.SKYNET_PORTAL_API}/skynet/skyfile`, {
body: form, body: form,
headers: { cookie: authCookie }, headers: {
"Skynet-Api-Key": process.env.ACCOUNTS_TEST_USER_API_KEY,
},
}); });
data.statusCode = response.statusCode; data.statusCode = response.statusCode;
@ -106,15 +109,14 @@ async function accountWebsiteCheck(done) {
// registryWriteAndReadCheck writes to registry and immediately reads and compares the data // registryWriteAndReadCheck writes to registry and immediately reads and compares the data
async function registryWriteAndReadCheck(done) { async function registryWriteAndReadCheck(done) {
const authCookie = await getAuthCookie();
const time = process.hrtime(); const time = process.hrtime();
const data = { name: "registry_write_and_read", up: false }; const data = { name: "registry_write_and_read", up: false };
const { privateKey, publicKey } = genKeyPairAndSeed(); const { privateKey, publicKey } = genKeyPairAndSeed();
const expected = { dataKey: "foo-key", data: stringToUint8ArrayUtf8("foo-data"), revision: BigInt(0) }; const expected = { dataKey: "foo-key", data: stringToUint8ArrayUtf8("foo-data"), revision: BigInt(0) };
try { try {
await skynetClient.registry.setEntry(privateKey, expected, { customCookie: authCookie }); await skynetClient.registry.setEntry(privateKey, expected);
const { entry } = await skynetClient.registry.getEntry(publicKey, expected.dataKey, { customCookie: authCookie }); const { entry } = await skynetClient.registry.getEntry(publicKey, expected.dataKey);
if (isEqual(expected, entry)) { if (isEqual(expected, entry)) {
data.up = true; data.up = true;
@ -204,12 +206,16 @@ async function blockerHealthCheck(done) {
} }
async function genericAccessCheck(name, url) { async function genericAccessCheck(name, url) {
const authCookie = await getAuthCookie();
const time = process.hrtime(); const time = process.hrtime();
const data = { up: false, url }; const data = { up: false, url };
try { try {
const response = await got(url, { headers: { cookie: `nocache=true;${authCookie}` } }); const response = await got(url, {
headers: {
cookie: "nocache=true",
"Skynet-Api-Key": process.env.ACCOUNTS_TEST_USER_API_KEY,
},
});
data.statusCode = response.statusCode; data.statusCode = response.statusCode;
data.up = true; data.up = true;

View File

@ -2,7 +2,7 @@ const got = require("got");
const hasha = require("hasha"); const hasha = require("hasha");
const { detailedDiff } = require("deep-object-diff"); const { detailedDiff } = require("deep-object-diff");
const { isEqual } = require("lodash"); const { isEqual } = require("lodash");
const { calculateElapsedTime, ensureValidJSON, getResponseContent, getAuthCookie } = require("../utils"); const { calculateElapsedTime, ensureValidJSON, getResponseContent } = require("../utils");
const { parseSkylink } = require("skynet-js"); const { parseSkylink } = require("skynet-js");
// audioExampleCheck returns the result of trying to download the skylink // audioExampleCheck returns the result of trying to download the skylink
@ -1130,13 +1130,18 @@ function parseHeaderString(header) {
// skylinkVerification verifies a skylink against provided information. // skylinkVerification verifies a skylink against provided information.
async function skylinkVerification(done, expected, { followRedirect = true, method = "get" } = {}) { async function skylinkVerification(done, expected, { followRedirect = true, method = "get" } = {}) {
const authCookie = await getAuthCookie();
const time = process.hrtime(); const time = process.hrtime();
const details = { name: expected.name, skylink: expected.skylink }; const details = { name: expected.name, skylink: expected.skylink };
try { try {
const query = `${process.env.SKYNET_PORTAL_API}/${expected.skylink}`; const query = `${process.env.SKYNET_PORTAL_API}/${expected.skylink}`;
const response = await got[method](query, { followRedirect, headers: { cookie: `nocache=true;${authCookie}` } }); const response = await got[method](query, {
followRedirect,
headers: {
cookie: "nocache=true",
"Skynet-Api-Key": process.env.ACCOUNTS_TEST_USER_API_KEY,
},
});
const entry = { ...details, up: true, statusCode: response.statusCode, time: calculateElapsedTime(time) }; const entry = { ...details, up: true, statusCode: response.statusCode, time: calculateElapsedTime(time) };
const info = {}; const info = {};

View File

@ -61,70 +61,6 @@ function getRequiredEnvironmentVariable(name) {
return value; return value;
} }
/**
* Authenticate with given credentials and return auth cookie
* Creates new account if username does not exist
* Only authenticates when portal is set to authenticated users only mode
* @param {boolean} forceAuth forcibly ensure authentication with test credentials
*/
function getAuthCookie(forceAuth = false) {
// cache auth promise so only one actual request will be made
if (getAuthCookie.cache) return getAuthCookie.cache;
// accounts disabled, do not try to authenticate
if (!isPortalModuleEnabled("a")) return "";
// do not authenticate if it is not required by portal limit access rule
if (!forceAuth && !["authenticated", "subscription"].includes(process.env.ACCOUNTS_LIMIT_ACCESS)) return "";
// assign all required environment variables
const portalDomain = getRequiredEnvironmentVariable("PORTAL_DOMAIN");
const email = getRequiredEnvironmentVariable("ACCOUNTS_TEST_USER_EMAIL");
const password = getRequiredEnvironmentVariable("ACCOUNTS_TEST_USER_PASSWORD");
async function authenticate() {
const got = require("got");
try {
// authenticate with given test user credentials
const response = await got.post(`https://account.${portalDomain}/api/login`, {
json: { email, password },
});
// extract set-cookie from successful authentication request
const cookies = response.headers["set-cookie"];
// throw meaningful error when set-cookie header is missing
if (!cookies) throw new Error(`Auth successful (code ${response.statusCode}) but 'set-cookie' header is missing`);
// find the skynet-jwt cookie
const jwtcookie = cookies.find((cookie) => cookie.startsWith("skynet-jwt"));
// throw meaningful error when skynet-jwt cookie is missing
if (!jwtcookie) throw new Error(`Header 'set-cookie' found but 'skynet-jwt' cookie is missing`);
// extract just the cookie value (no set-cookie props) from set-cookie
return jwtcookie.match(/skynet-jwt=[^;]+;/)[0];
} catch (error) {
// 401 means that service worked but user could not have been authenticated
if (error.response && error.response.statusCode === 401) {
// sign up with the given credentials
await got.post(`https://account.${portalDomain}/api/user`, {
json: { email, password },
});
// retry authentication
return authenticate();
}
// rethrow unhandled exception
throw error;
}
}
return (getAuthCookie.cache = authenticate());
}
/** /**
* isPortalModuleEnabled returns true if the given module is enabled * isPortalModuleEnabled returns true if the given module is enabled
*/ */
@ -137,7 +73,6 @@ module.exports = {
getYesterdayISOString, getYesterdayISOString,
getResponseContent, getResponseContent,
ensureValidJSON, ensureValidJSON,
getAuthCookie,
isPortalModuleEnabled, isPortalModuleEnabled,
ipCheckService, ipCheckService,
ipRegex, ipRegex,