set up auth on the server + test utils
This commit is contained in:
parent
f3013c2956
commit
12b87ee7d1
7 changed files with 166 additions and 10 deletions
|
@ -24,6 +24,8 @@
|
|||
"honeycomb-beeline": "^2.2.0",
|
||||
"immer": "^6.0.3",
|
||||
"jimp": "^0.14.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwks-rsa": "^1.9.0",
|
||||
"mysql2": "^2.1.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"react": "^16.13.1",
|
||||
|
|
|
@ -17,8 +17,8 @@ const { normalizeRow } = require("../src/server/util");
|
|||
|
||||
const auth0 = new ManagementClient({
|
||||
domain: "openneo.us.auth0.com",
|
||||
clientId: process.env.AUTH0_CLIENT_ID,
|
||||
clientSecret: process.env.AUTH0_CLIENT_SECRET,
|
||||
clientId: process.env.AUTH0_SUPPORT_CLIENT_ID,
|
||||
clientSecret: process.env.AUTH0_SUPPORT_CLIENT_SECRET,
|
||||
scope: "read:users update:users",
|
||||
});
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ function App() {
|
|||
domain="openneo.us.auth0.com"
|
||||
clientId="8LjFauVox7shDxVufQqnviUIywMuuC4r"
|
||||
redirectUri={window.location.origin}
|
||||
audience="https://impress-2020.openneo.net/api"
|
||||
scope=""
|
||||
>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<ChakraProvider theme={theme}>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
const { gql, makeExecutableSchema } = require("apollo-server");
|
||||
const util = require("util");
|
||||
|
||||
const { addBeelineToSchema, beelinePlugin } = require("./lib/beeline-graphql");
|
||||
const { gql, makeExecutableSchema } = require("apollo-server");
|
||||
const jwtVerify = util.promisify(require("jsonwebtoken").verify);
|
||||
const jwksClient = require("jwks-rsa");
|
||||
|
||||
const connectToDb = require("./db");
|
||||
const buildLoaders = require("./loaders");
|
||||
|
@ -262,6 +266,7 @@ const typeDefs = gql`
|
|||
species(id: ID!): Species
|
||||
|
||||
user(id: ID!): User
|
||||
currentUser: User
|
||||
|
||||
petOnNeopetsDotCom(petName: String!): Outfit
|
||||
}
|
||||
|
@ -714,6 +719,23 @@ const resolvers = {
|
|||
|
||||
return { id };
|
||||
},
|
||||
currentUser: async (_, __, { currentUserId, userLoader }) => {
|
||||
if (currentUserId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await userLoader.load(currentUserId);
|
||||
} catch (e) {
|
||||
if (e.message.includes("could not find user")) {
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return { id: currentUserId };
|
||||
},
|
||||
petOnNeopetsDotCom: async (_, { petName }) => {
|
||||
const [petMetaData, customPetData] = await Promise.all([
|
||||
neopets.loadPetMetaData(petName),
|
||||
|
@ -1260,9 +1282,49 @@ if (process.env["NODE_ENV"] !== "test") {
|
|||
plugins.push(beelinePlugin);
|
||||
}
|
||||
|
||||
const jwks = jwksClient({
|
||||
jwksUri: "https://openneo.us.auth0.com/.well-known/jwks.json",
|
||||
});
|
||||
|
||||
async function getJwtKey(header, callback) {
|
||||
jwks.getSigningKey(header.kid, (err, key) => {
|
||||
if (err) {
|
||||
return callback(null, signingKey);
|
||||
}
|
||||
const signingKey = key.publicKey || key.rsaPublicKey;
|
||||
callback(null, signingKey);
|
||||
});
|
||||
}
|
||||
|
||||
async function getUserIdFromToken(token) {
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = await jwtVerify(token, getJwtKey, {
|
||||
audience: "https://impress-2020.openneo.net/api",
|
||||
issuer: "https://openneo.us.auth0.com/",
|
||||
algorithms: ["RS256"],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`Invalid auth token: ${token}\n${e}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const userId = payload.sub.match(/auth0\|impress-([0-9]+)/)?.[1];
|
||||
if (!userId) {
|
||||
console.log("Unexpected auth token sub format", payload.sub);
|
||||
return null;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
const config = {
|
||||
schema,
|
||||
context: async () => {
|
||||
context: async ({ req }) => {
|
||||
const db = await connectToDb();
|
||||
|
||||
const svgLogger = {
|
||||
|
@ -1273,9 +1335,13 @@ const config = {
|
|||
};
|
||||
lastSvgLogger = svgLogger;
|
||||
|
||||
const token = req.headers.authorization?.match(/^Bearer (.+)$/)?.[1];
|
||||
const currentUserId = await getUserIdFromToken(token);
|
||||
|
||||
return {
|
||||
svgLogger,
|
||||
db,
|
||||
currentUserId,
|
||||
...buildLoaders(db),
|
||||
};
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const gql = require("graphql-tag");
|
||||
const { query, getDbCalls } = require("./setup.js");
|
||||
const { query, getDbCalls, logInAsTestUser } = require("./setup.js");
|
||||
|
||||
describe("User", () => {
|
||||
it("looks up a user", async () => {
|
||||
|
@ -48,7 +48,7 @@ describe("User", () => {
|
|||
});
|
||||
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data.user).toBe(null);
|
||||
expect(res.data).toEqual({ user: null });
|
||||
expect(getDbCalls()).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
|
@ -60,4 +60,56 @@ describe("User", () => {
|
|||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it("gets current user, if logged in", async () => {
|
||||
await logInAsTestUser();
|
||||
|
||||
const res = await query({
|
||||
query: gql`
|
||||
query {
|
||||
currentUser {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"currentUser": Object {
|
||||
"id": "44743",
|
||||
"username": "dti-test",
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(getDbCalls()).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"SELECT * FROM users WHERE id IN (?)",
|
||||
Array [
|
||||
"44743",
|
||||
],
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it("gets no user, if logged out", async () => {
|
||||
const res = await query({
|
||||
query: gql`
|
||||
query {
|
||||
currentUser {
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(res).toHaveNoErrors();
|
||||
expect(res.data).toEqual({ currentUser: null });
|
||||
expect(getDbCalls()).toMatchInlineSnapshot(`Array []`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,28 @@
|
|||
const { ApolloServer } = require("apollo-server");
|
||||
const { createTestClient } = require("apollo-server-testing");
|
||||
const { AuthenticationClient } = require("auth0");
|
||||
|
||||
const connectToDb = require("../db");
|
||||
const actualConnectToDb = jest.requireActual("../db");
|
||||
const { config } = require("../index");
|
||||
|
||||
const { query } = createTestClient(new ApolloServer(config));
|
||||
let accessTokenForQueries = null;
|
||||
|
||||
const { query } = createTestClient(
|
||||
new ApolloServer({
|
||||
...config,
|
||||
context: () =>
|
||||
config.context({
|
||||
req: {
|
||||
headers: {
|
||||
authorization: accessTokenForQueries
|
||||
? `Bearer ${accessTokenForQueries}`
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// Spy on db.execute, so we can snapshot the queries we run. This can help us
|
||||
// keep an eye on perf - watch for tests with way too many queries!
|
||||
|
@ -19,7 +36,8 @@ beforeAll(() => {
|
|||
return db;
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
beforeEach(() => {
|
||||
accessTokenForQueries = null;
|
||||
if (dbExecuteFn) {
|
||||
dbExecuteFn.mockClear();
|
||||
}
|
||||
|
@ -31,6 +49,22 @@ afterAll(() => {
|
|||
});
|
||||
const getDbCalls = () => (dbExecuteFn ? dbExecuteFn.mock.calls : []);
|
||||
|
||||
async function logInAsTestUser() {
|
||||
const auth0 = new AuthenticationClient({
|
||||
domain: "openneo.us.auth0.com",
|
||||
clientId: process.env.AUTH0_TEST_CLIENT_ID,
|
||||
clientSecret: process.env.AUTH0_TEST_CLIENT_SECRET,
|
||||
});
|
||||
|
||||
const res = await auth0.passwordGrant({
|
||||
username: "dti-test",
|
||||
password: process.env.DTI_TEST_USER_PASSWORD,
|
||||
audience: "https://impress-2020.openneo.net/api",
|
||||
});
|
||||
|
||||
accessTokenForQueries = res.access_token;
|
||||
}
|
||||
|
||||
// Add a new `expect(res).toHaveNoErrors()` to call after GraphQL calls!
|
||||
expect.extend({
|
||||
toHaveNoErrors(res) {
|
||||
|
@ -49,4 +83,4 @@ expect.extend({
|
|||
},
|
||||
});
|
||||
|
||||
module.exports = { query, getDbCalls };
|
||||
module.exports = { query, getDbCalls, logInAsTestUser };
|
||||
|
|
|
@ -8547,7 +8547,7 @@ jwa@^1.4.1:
|
|||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jwks-rsa@^1.8.0:
|
||||
jwks-rsa@^1.8.0, jwks-rsa@^1.9.0:
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-1.9.0.tgz#efa0cd550c13b70397e27cd8764bd53c45a1ad91"
|
||||
integrity sha512-UPCfQQg0s2kF2Ju6UFJrQH73f7MaVN/hKBnYBYOp+X9KN4y6TLChhLtaXS5nRKbZqshwVdrZ9OY63m/Q9CLqcg==
|
||||
|
|
Loading…
Reference in a new issue