2021-02-02 22:26:55 -08:00
|
|
|
import { beelinePlugin } from "./lib/beeline-graphql";
|
|
|
|
import { gql, makeExecutableSchema } from "apollo-server";
|
2022-08-17 15:24:17 -07:00
|
|
|
import { getUserIdFromToken as getUserIdFromTokenViaAuth0 } from "./auth";
|
2021-02-02 22:26:55 -08:00
|
|
|
import connectToDb from "./db";
|
|
|
|
import buildLoaders from "./loaders";
|
|
|
|
import { plugin as cacheControlPluginFork } from "./lib/apollo-cache-control-fork";
|
2022-08-17 15:24:17 -07:00
|
|
|
import {
|
|
|
|
getAuthToken,
|
|
|
|
getUserIdFromToken as getUserIdFromTokenViaDb,
|
|
|
|
} from "./auth-by-db";
|
2020-04-22 11:51:36 -07:00
|
|
|
|
2020-09-06 01:51:58 -07:00
|
|
|
const rootTypeDefs = gql`
|
2021-02-02 17:51:54 -08:00
|
|
|
enum CacheScope {
|
|
|
|
PUBLIC
|
|
|
|
PRIVATE
|
|
|
|
}
|
|
|
|
directive @cacheControl(
|
|
|
|
maxAge: Int
|
2021-02-02 19:07:38 -08:00
|
|
|
staleWhileRevalidate: Int
|
2021-02-02 17:51:54 -08:00
|
|
|
scope: CacheScope
|
|
|
|
) on FIELD_DEFINITION | OBJECT
|
2020-08-16 23:28:41 -07:00
|
|
|
|
2020-09-06 01:51:58 -07:00
|
|
|
type Mutation
|
|
|
|
type Query
|
2020-04-22 11:51:36 -07:00
|
|
|
`;
|
|
|
|
|
2020-09-06 01:51:58 -07:00
|
|
|
function mergeTypeDefsAndResolvers(modules) {
|
|
|
|
const allTypeDefs = [];
|
|
|
|
const allResolvers = {};
|
2020-08-01 15:30:26 -07:00
|
|
|
|
2020-09-06 01:51:58 -07:00
|
|
|
for (const { typeDefs, resolvers } of modules) {
|
|
|
|
allTypeDefs.push(typeDefs);
|
|
|
|
for (const typeName of Object.keys(resolvers)) {
|
|
|
|
allResolvers[typeName] = {
|
|
|
|
...allResolvers[typeName],
|
|
|
|
...resolvers[typeName],
|
2020-04-25 06:50:34 -07:00
|
|
|
};
|
2020-09-06 01:51:58 -07:00
|
|
|
}
|
|
|
|
}
|
2020-05-23 11:32:05 -07:00
|
|
|
|
2020-09-06 01:51:58 -07:00
|
|
|
return { typeDefs: allTypeDefs, resolvers: allResolvers };
|
|
|
|
}
|
2020-05-23 11:32:05 -07:00
|
|
|
|
2020-09-06 01:51:58 -07:00
|
|
|
const schema = makeExecutableSchema(
|
|
|
|
mergeTypeDefsAndResolvers([
|
|
|
|
{ typeDefs: rootTypeDefs, resolvers: {} },
|
|
|
|
require("./types/AppearanceLayer"),
|
2020-11-24 14:24:34 -08:00
|
|
|
require("./types/ClosetList"),
|
2020-09-06 01:51:58 -07:00
|
|
|
require("./types/Item"),
|
|
|
|
require("./types/MutationsForSupport"),
|
|
|
|
require("./types/Outfit"),
|
2020-10-06 05:04:44 -07:00
|
|
|
require("./types/Pet"),
|
2020-09-06 01:51:58 -07:00
|
|
|
require("./types/PetAppearance"),
|
|
|
|
require("./types/User"),
|
|
|
|
require("./types/Zone"),
|
|
|
|
])
|
|
|
|
);
|
|
|
|
|
2021-02-02 19:07:38 -08:00
|
|
|
const plugins = [cacheControlPluginFork({ calculateHttpHeaders: true })];
|
2020-08-17 01:27:05 -07:00
|
|
|
|
|
|
|
if (process.env["NODE_ENV"] !== "test") {
|
|
|
|
plugins.push(beelinePlugin);
|
|
|
|
}
|
2020-08-16 23:28:41 -07:00
|
|
|
|
2020-04-22 13:03:32 -07:00
|
|
|
const config = {
|
2020-08-16 23:28:41 -07:00
|
|
|
schema,
|
2022-08-17 00:58:52 -07:00
|
|
|
context: async ({ req, res }) => {
|
2020-04-22 11:51:36 -07:00
|
|
|
const db = await connectToDb();
|
2020-05-23 11:32:05 -07:00
|
|
|
|
2022-08-17 15:24:17 -07:00
|
|
|
let authMode = req.headers["dti-auth-mode"] || "auth0";
|
|
|
|
let currentUserId;
|
|
|
|
if (authMode === "auth0") {
|
|
|
|
const auth = (req && req.headers && req.headers.authorization) || "";
|
|
|
|
const authMatch = auth.match(/^Bearer (.+)$/);
|
|
|
|
const token = authMatch && authMatch[1];
|
|
|
|
currentUserId = await getUserIdFromTokenViaAuth0(token);
|
|
|
|
} else if (authMode === "db") {
|
|
|
|
currentUserId = await getCurrentUserIdViaDb(req);
|
|
|
|
} else {
|
|
|
|
console.warn(
|
|
|
|
`Unexpected auth mode: ${JSON.stringify(authMode)}. Skipping auth.`
|
|
|
|
);
|
|
|
|
currentUserId = null;
|
|
|
|
}
|
2020-09-02 23:00:16 -07:00
|
|
|
|
2020-04-22 12:00:52 -07:00
|
|
|
return {
|
2020-08-01 00:04:11 -07:00
|
|
|
db,
|
2020-09-02 23:00:16 -07:00
|
|
|
currentUserId,
|
2022-08-17 00:58:52 -07:00
|
|
|
login: async (params) => {
|
|
|
|
const authToken = await getAuthToken(params, db);
|
|
|
|
if (authToken == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const oneWeekFromNow = new Date();
|
|
|
|
oneWeekFromNow.setDate(oneWeekFromNow.getDate() + 7);
|
|
|
|
res.setHeader(
|
|
|
|
"Set-Cookie",
|
|
|
|
`DTIAuthToken=${encodeURIComponent(JSON.stringify(authToken))}; ` +
|
|
|
|
`Max-Age=${60 * 60 * 24 * 7}; Secure; HttpOnly; SameSite=Strict`
|
|
|
|
);
|
|
|
|
return authToken;
|
|
|
|
},
|
2022-08-17 16:05:36 -07:00
|
|
|
logout: async () => {
|
|
|
|
// NOTE: This function isn't actually async in practice, but we mark it
|
|
|
|
// as such for consistency with `login`!
|
|
|
|
// Set a header to delete the cookie. (That is, empty and expired.)
|
|
|
|
res.setHeader("Set-Cookie", `DTIAuthToken=; Max-Age=-1`);
|
|
|
|
},
|
2020-04-23 14:23:46 -07:00
|
|
|
...buildLoaders(db),
|
2020-04-22 12:00:52 -07:00
|
|
|
};
|
2020-04-22 11:51:36 -07:00
|
|
|
},
|
2021-11-23 13:00:56 -08:00
|
|
|
formatResponse: (res, context) => {
|
|
|
|
// The Authorization header can affect the response, so we signal that here
|
|
|
|
// for caching user data! That way, login/logout will refresh user data,
|
|
|
|
// even if it was briefly cached.
|
|
|
|
//
|
|
|
|
// NOTE: Our frontend JS only sends the Authorization header for user data
|
|
|
|
// queries. For public data, the header will be absent, and different
|
|
|
|
// users will still be able to share the same public cache data.
|
|
|
|
//
|
|
|
|
// NOTE: At time of writing, I'm not sure we use this in app? I think all
|
|
|
|
// current user data queries request fields with `maxAge: 0`. But I'm
|
|
|
|
// adding it just to remove a potential surprise gotcha later!
|
|
|
|
context.response.http.headers.set("Vary", "Authorization");
|
|
|
|
|
|
|
|
return res;
|
|
|
|
},
|
2020-04-23 01:09:17 -07:00
|
|
|
|
2020-08-17 01:27:05 -07:00
|
|
|
plugins,
|
2020-05-23 11:32:05 -07:00
|
|
|
|
2021-02-02 19:07:38 -08:00
|
|
|
// We use our own fork of the cacheControl plugin!
|
|
|
|
cacheControl: false,
|
|
|
|
|
2020-04-23 01:09:17 -07:00
|
|
|
// Enable Playground in production :)
|
|
|
|
introspection: true,
|
2020-04-23 01:12:52 -07:00
|
|
|
playground: {
|
|
|
|
endpoint: "/api/graphql",
|
|
|
|
},
|
2020-04-22 13:03:32 -07:00
|
|
|
};
|
2020-04-22 11:51:36 -07:00
|
|
|
|
2022-08-17 15:24:17 -07:00
|
|
|
async function getCurrentUserIdViaDb(req) {
|
|
|
|
const authTokenCookieString = req.cookies.DTIAuthToken;
|
|
|
|
if (!authTokenCookieString) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let authTokenFromCookie = null;
|
|
|
|
try {
|
|
|
|
authTokenFromCookie = JSON.parse(authTokenCookieString);
|
|
|
|
} catch (error) {
|
|
|
|
console.warn(`DTIAuthToken cookie was not valid JSON, ignoring.`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return await getUserIdFromTokenViaDb(authTokenFromCookie);
|
|
|
|
}
|
|
|
|
|
2020-04-22 11:51:36 -07:00
|
|
|
if (require.main === module) {
|
2020-04-22 13:03:32 -07:00
|
|
|
const { ApolloServer } = require("apollo-server");
|
|
|
|
const server = new ApolloServer(config);
|
2020-04-22 11:51:36 -07:00
|
|
|
server.listen().then(({ url }) => {
|
2021-05-03 15:01:49 -07:00
|
|
|
console.info(`🚀 Server ready at ${url}`);
|
2020-04-22 11:51:36 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-04-22 13:03:32 -07:00
|
|
|
module.exports = { config };
|