1 | import body from "koa-body";
|
2 | import v4 from "uuid/v4";
|
3 | import createPlaygroundMiddleware from "graphql-playground-middleware-koa";
|
4 | import Router, { IRouterOptions } from "koa-router";
|
5 | import { errorHandler, execute } from "graphql-api-koa";
|
6 | import { Context as KoaContext, Next as KoaNext } from "koa";
|
7 | import { parse } from "auth-header";
|
8 | import { Pool } from "pg";
|
9 |
|
10 | import x from "./x";
|
11 | import oauth2 from "./oauth2";
|
12 | import { Config, assertConfig } from "./Config";
|
13 | import { Context } from "./Context";
|
14 | import { createSchema } from "./graphql";
|
15 | import { fromBasic, fromBearer } from "./util/getAuthorization";
|
16 | import { StrategyCollection } from "./StrategyCollection";
|
17 | import { UnsupportedMediaTypeError } from "./errors";
|
18 | import { createAuthXExplanations } from "./explanations";
|
19 |
|
20 | export * from "./x";
|
21 | export * from "./errors";
|
22 | export * from "./model";
|
23 | export * from "./graphql";
|
24 | export * from "./Strategy";
|
25 | export * from "./StrategyCollection";
|
26 | export * from "./Config";
|
27 | export * from "./Context";
|
28 | export * from "./util/validateIdFormat";
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | type AuthXMiddleware = any;
|
34 |
|
35 | export class AuthX extends Router<any, { [x]: Context }> {
|
36 | public readonly pool: Pool;
|
37 | public constructor(config: Config & IRouterOptions) {
|
38 | assertConfig(config);
|
39 | super(config);
|
40 |
|
41 | const explanations = createAuthXExplanations({ [config.realm]: "AuthX" });
|
42 |
|
43 | const strategies =
|
44 | config.strategies instanceof StrategyCollection
|
45 | ? config.strategies
|
46 | : new StrategyCollection(config.strategies);
|
47 |
|
48 |
|
49 | this.pool = new Pool(config.pg);
|
50 |
|
51 |
|
52 | const contextMiddleware = async (
|
53 | ctx: KoaContext & { [x]: Context },
|
54 | next: KoaNext
|
55 | ): Promise<void> => {
|
56 | const tx = await this.pool.connect();
|
57 | try {
|
58 | let authorization = null;
|
59 |
|
60 | const auth = ctx.request.header.authorization
|
61 | ? parse(ctx.request.header.authorization)
|
62 | : null;
|
63 |
|
64 |
|
65 | const basic =
|
66 | auth && auth.scheme === "Basic" && typeof auth.token === "string"
|
67 | ? auth.token
|
68 | : null;
|
69 |
|
70 | if (basic) {
|
71 | authorization = await fromBasic(tx, basic);
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | await authorization.invoke(tx, {
|
77 | id: v4(),
|
78 | format: "basic",
|
79 | createdAt: new Date()
|
80 | });
|
81 | }
|
82 |
|
83 |
|
84 | const bearer =
|
85 | auth && auth.scheme === "Bearer" && typeof auth.token === "string"
|
86 | ? auth.token
|
87 | : null;
|
88 |
|
89 | if (bearer) {
|
90 | authorization = await fromBearer(tx, config.publicKeys, bearer);
|
91 |
|
92 |
|
93 |
|
94 | }
|
95 |
|
96 |
|
97 | if (ctx.request.header.authorization && !authorization) {
|
98 | throw new Error(
|
99 | "An authorization header must be of either HTTP Basic or Bearer format."
|
100 | );
|
101 | }
|
102 |
|
103 | const context: Context = {
|
104 | ...ctx[x],
|
105 | ...config,
|
106 | strategies,
|
107 | authorization,
|
108 | pool: this.pool,
|
109 | explanations: explanations
|
110 | };
|
111 |
|
112 | ctx[x] = context;
|
113 | } finally {
|
114 | tx.release();
|
115 | }
|
116 |
|
117 | await next();
|
118 | };
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 | this.post(
|
125 | "/graphql",
|
126 |
|
127 | errorHandler(),
|
128 |
|
129 | contextMiddleware as AuthXMiddleware,
|
130 |
|
131 |
|
132 |
|
133 | async (ctx, next) => {
|
134 | if (!ctx.is("json"))
|
135 | throw new UnsupportedMediaTypeError(
|
136 | "Requests to the AuthX GraphQL endpoint MUST specify a Content-Type of `application/json`."
|
137 | );
|
138 |
|
139 | await next();
|
140 | },
|
141 |
|
142 | body({ multipart: false, urlencoded: false, text: false, json: true }),
|
143 |
|
144 | execute({
|
145 | schema: config.processSchema
|
146 | ? config.processSchema(createSchema(strategies))
|
147 | : createSchema(strategies),
|
148 | override: (ctx: any) => {
|
149 | const contextValue: Context = ctx[x];
|
150 |
|
151 | return {
|
152 | contextValue
|
153 | };
|
154 | }
|
155 | })
|
156 | );
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | this.all("/graphiql", createPlaygroundMiddleware({ endpoint: "/graphql" }));
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | this.post(
|
176 | "/",
|
177 | contextMiddleware as AuthXMiddleware,
|
178 | body({ multipart: false, urlencoded: true, text: false, json: true }),
|
179 | oauth2 as AuthXMiddleware
|
180 | );
|
181 | }
|
182 | }
|
183 |
|
184 | export default AuthX;
|