Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: authentication plugin; drop support for meteor-login-token header #5999

Merged
merged 4 commits into from
Jan 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions .reaction/getAuthToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
const { URL } = require("url");
const fetch = require("node-fetch");
const simpleOAuth2 = require("simple-oauth2");

const HYDRA_OAUTH_URL = "http://localhost:4444";
const HYDRA_ADMIN_URL = "http://localhost:4445";
const OAUTH2_CLIENT_ID = "get-token-dev-script";
const OAUTH2_CLIENT_SECRET = "get-token-dev-script-secret";
const FAKE_REDIRECT_URI = "http://localhost:1234/redirect";

// Initialize the OAuth2 Library
const oauth2 = simpleOAuth2.create({
client: {
id: OAUTH2_CLIENT_ID,
secret: OAUTH2_CLIENT_SECRET
},
auth: {
authorizePath: "/oauth2/auth",
tokenHost: HYDRA_OAUTH_URL,
tokenPath: "/oauth2/token"
}
});

const makeAbsolute = (relativeUrl, baseUrl) => {
const url = new URL(relativeUrl, baseUrl);
return url.href;
};

/* eslint-disable camelcase */
const hydraClient = {
client_id: OAUTH2_CLIENT_ID,
client_secret: OAUTH2_CLIENT_SECRET,
grant_types: ["authorization_code"],
redirect_uris: [FAKE_REDIRECT_URI],
response_types: ["code"],
scope: "offline openid",
subject_type: "public",
token_endpoint_auth_method: "client_secret_basic"
};
/* eslint-enable camelcase */

/**
* @summary Calls Hydra's endpoint to create an OAuth client for this application
* if one does not already exist. This works because the Hydra admin port
* is exposed on the internal network. Ensure that it is not exposed to the
* public Internet in production.
* @returns {Promise<undefined>} Nothing
*/
async function ensureHydraClient() {
const getClientResponse = await fetch(makeAbsolute(`/clients/${OAUTH2_CLIENT_ID}`, HYDRA_ADMIN_URL), {
method: "GET",
headers: { "Content-Type": "application/json" }
});

if (![200, 404].includes(getClientResponse.status)) {
console.error(await getClientResponse.text());
throw new Error(`Could not get Hydra client [${getClientResponse.status}]`);
}

if (getClientResponse.status === 200) {
// Update the client to be sure it has the latest config
const updateClientResponse = await fetch(makeAbsolute(`clients/${OAUTH2_CLIENT_ID}`, HYDRA_ADMIN_URL), {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(hydraClient)
});

if (updateClientResponse.status !== 200) {
console.error(await updateClientResponse.text());
throw new Error(`Could not update Hydra client [${updateClientResponse.status}]`);
}
} else {
const response = await fetch(makeAbsolute("/clients", HYDRA_ADMIN_URL), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(hydraClient)
});

switch (response.status) {
case 200:
// intentional fallthrough!
// eslint-disable-line no-fallthrough
case 201:
// intentional fallthrough!
// eslint-disable-line no-fallthrough
case 409:
break;
default:
console.error(await response.text());
throw new Error(`Could not create Hydra client [${response.status}]`);
}
}
}

async function main(userId) {
await ensureHydraClient();

const authorizationUri = oauth2.authorizationCode.authorizeURL({
redirect_uri: FAKE_REDIRECT_URI,
scope: "openid",
state: "12345678"
});

const startLoginResult = await fetch(authorizationUri, {
redirect: "manual"
});
const redirect1 = startLoginResult.headers.get("location");
const redirect1Parsed = new URL(redirect1);

if (redirect1.includes("oauth-error")) {
console.error(redirect1Parsed.searchParams.get("error_hint"));
return;
}

const challenge = redirect1Parsed.searchParams.get("login_challenge");
const cookie = startLoginResult.headers.get("set-cookie");

const acceptLoginResult = await fetch(`${HYDRA_ADMIN_URL}/oauth2/auth/requests/login/accept?login_challenge=${challenge}`, {
method: "PUT",
body: JSON.stringify({
subject: userId,
remember: false
}),
headers: {
"Content-Type": "application/json"
}
});

const { redirect_to: redirect2 } = await acceptLoginResult.json();

const continueLoginResult = await fetch(redirect2, {
headers: {
"Cookie": cookie
},
redirect: "manual"
});
const redirect3 = continueLoginResult.headers.get("location");
const redirect3Parsed = new URL(redirect3);

if (redirect3.includes("error_debug")) {
console.error(redirect3Parsed.searchParams.get("error_debug"));
return;
}

const consentChallenge = redirect3Parsed.searchParams.get("consent_challenge");
const nextCookies = continueLoginResult.headers.raw()['set-cookie']

const consentResult = await fetch(`${HYDRA_ADMIN_URL}/oauth2/auth/requests/consent/accept?consent_challenge=${consentChallenge}`, {
method: "PUT",
body: JSON.stringify({
grant_scope: ["openid"],
remember: false
}),
headers: {
"Content-Type": "application/json"
}
});

const { redirect_to: redirect4 } = await consentResult.json();

const postConsentResult = await fetch(redirect4, {
headers: nextCookies.map((val) => (["Cookie", val])),
redirect: "manual"
});
const redirect5 = postConsentResult.headers.get("location");
const redirect5Parsed = new URL(redirect5);

if (redirect5.includes("error_debug")) {
console.error(redirect5Parsed.searchParams.get("error_debug"));
return;
}

const code = redirect5Parsed.searchParams.get("code");

const { access_token: accessToken } = await oauth2.authorizationCode.getToken({
code,
redirect_uri: FAKE_REDIRECT_URI,
scope: "openid"
});

console.log(`\nAccess token for user ${userId}:\n\n${accessToken}\n`);
console.log(`Paste this in GraphQL Playground "HTTP HEADERS" box:\n{\n "Authorization": "${accessToken}"\n}\n`);

// server.close();
}

const userId = process.argv[2];
if (!userId) {
console.error("Usage: node getAuthToken.js [userId]");
process.exit(1);
}

main(userId).catch((error) => {
console.error(error.message);
console.error("\nMake sure the Hydra service is running with ports 4444 and 4445 accessible from the host computer\n");
});
34 changes: 34 additions & 0 deletions bin/token
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash

# Please Use Google Shell Style: https://google.github.io/styleguide/shell.xml

# ---- Start unofficial bash strict mode boilerplate
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -o errexit # always exit on error
set -o errtrace # trap errors in functions as well
set -o pipefail # don't ignore exit codes when piping output
set -o posix # more strict failures in subshells
# set -x # enable debugging

IFS="$(printf "\n\t")"
# ---- End unofficial bash strict mode boilerplate

if [[ "$#" -ne 1 ]]; then
echo "Usage: $0 [userId]" 1>&2
exit 1
fi

__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
node_script=${__dir}/../.reaction/getAuthToken.js
simple_oauth2_pkg_json=${__dir}/../node_modules/simple-oauth2/package.json
simple_oauth2_version="3.1.0"

# NPM install the simple-oauth2 package used by the script only if necessary
if [[ ! -f ${simple_oauth2_pkg_json} ]]; then
npm install --no-save simple-oauth2@${simple_oauth2_version}
elif [[ $(grep -c '"version": "'${simple_oauth2_version}'"' ${simple_oauth2_pkg_json}) != "1" ]]; then
npm install --no-save simple-oauth2@${simple_oauth2_version}
fi

# Run the script using Node
node ${node_script} "$1"
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"node-fetch": "~2.6.0",
"object-hash": "~1.3.1",
"promise-retry": "^1.1.1",
"querystring": "~0.2.0",
"ramda": "~0.26.1",
"semver": "~6.3.0",
"sharp": "~0.23.1",
Expand Down
1 change: 0 additions & 1 deletion src/core-services/account/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import envalid from "envalid";
const { str } = envalid;

export default envalid.cleanEnv(process.env, {
HYDRA_OAUTH2_INTROSPECT_URL: str({ devDefault: "http://hydra:4445/oauth2/introspect" }),
NODE_ENV: str({ default: "production" }),
REACTION_IDENTITY_PUBLIC_PASSWORD_RESET_URL: str({ devDefault: "http://localhost:4100/account/reset-password/TOKEN" }),
REACTION_IDENTITY_PUBLIC_VERIFY_EMAIL_URL: str({ devDefault: "http://localhost:4100/#/verify-email/TOKEN" }),
Expand Down
8 changes: 0 additions & 8 deletions src/core-services/account/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import queries from "./queries/index.js";
import resolvers from "./resolvers/index.js";
import schemas from "./schemas/index.js";
import startup from "./startup.js";
import tokenMiddleware from "./util/tokenMiddleware.js";
import accountByUserId from "./util/accountByUserId.js";
import { Account } from "./simpleSchemas.js";

Expand Down Expand Up @@ -61,13 +60,6 @@ export default async function register(app) {
mutations,
queries,
policies,
expressMiddleware: [
{
route: "graphql",
stage: "authenticate",
fn: tokenMiddleware
}
],
simpleSchemas: {
Account
}
Expand Down
4 changes: 2 additions & 2 deletions src/core-services/account/mutations/addAccountToGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default async function addAccountToGroup(context, input) {
Groups,
users
},
user
userId
} = context;

const groupToMoveUserTo = await Groups.findOne({ _id: groupId });
Expand Down Expand Up @@ -103,7 +103,7 @@ export default async function addAccountToGroup(context, input) {

await appEvents.emit("afterAccountUpdate", {
account: updatedAccount,
updatedBy: user._id,
updatedBy: userId,
updatedFields: ["groups"]
});

Expand Down
47 changes: 0 additions & 47 deletions src/core-services/account/util/getUserFromMeteorToken.js

This file was deleted.

60 changes: 0 additions & 60 deletions src/core-services/account/util/getUserFromMeteorToken.test.js

This file was deleted.

Loading