-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
148 lines (119 loc) · 3.58 KB
/
server.js
File metadata and controls
148 lines (119 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
require("isomorphic-fetch");
const dotenv = require("dotenv");
dotenv.config();
const mongoose = require("mongoose");
const fs = require("fs");
const Koa = require("koa");
const next = require("next");
const Merchant = require("./models/merchant");
const deoWebhookRouter = require("./routes/deoWebhook");
const apiRouter = require("./routes/api");
const gdprWebhook = require("./routes/shopifyWebhook");
const logger = require("./utils/logger");
const morgan = require("koa-morgan");
const { randomBytes } = require("crypto");
const { stringify } = require("querystring");
const authRouter = require("./routes/auth");
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
const { API_SECRET_KEY, API_KEY, DATABASE_URL, SCOPE, MY_DOMAIN } = process.env;
//Connect to database. Exit on failure
mongoose.connect(DATABASE_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
});
// Output for logs
const accessLogStream = fs.createWriteStream(__dirname + '/logs/morgan.log', { flags: 'a' });
const database = mongoose.connection;
// Exit on connection error
database.on('error', error => {
console.error(error);
process.exit(1);
});
database.once('open', () => {
app.prepare().then( async () => {
const server = new Koa();
server.keys = [API_SECRET_KEY];
server.on("error", handleError);
//Error handling and log all http requests
server
.use(catchThrowError)
.use(morgan("combined", { stream: accessLogStream }));
//Use koa router for simvoly webhooks
server
.use(deoWebhookRouter.routes())
.use(deoWebhookRouter.allowedMethods());
//Use koa router for GDPR webhooks
server
.use(gdprWebhook.routes())
.use(gdprWebhook.allowedMethods());
server
.use(apiRouter.routes())
.use(apiRouter.allowedMethods());
server
.use(authRouter.routes())
.use(authRouter.allowedMethods());
server.use(authenticate);
server
.use(apiRouter.routes())
.use(apiRouter.allowedMethods());
//Handle request with Nextjs
server.use(handleRequest);
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
});
async function authenticate(ctx, next) {
const shop = ctx.query.shop;
if(shop != null) {
//TODO verify hmac
const needsOAuth = !(await Merchant.exists({ shop, installed: true, scopes: SCOPE }));
if(needsOAuth) {
const redirectUrl = await getRedirectUrl(shop);
return ctx.redirect(redirectUrl);
}
}
await next();
}
//If a middleware throws an error catch it and return the error
async function catchThrowError(ctx, next) {
try {
await next();
} catch (error) {
ctx.status = error.status || 500;
ctx.app.emit("error", error, ctx);
}
}
//Handles errors thrown by the koa server
function handleError(error){
switch(error.message) {
default:
logger.log({level: 'error', message: error});
}
}
//Use nextjs request handler
async function handleRequest(ctx) {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
return;
}
async function getRedirectUrl(shop) {
const nonce = randomBytes(10).toString('hex');
const query = {
client_id: API_KEY,
scope: SCOPE,
redirect_uri: `https://${MY_DOMAIN}/auth/callback`,
state: nonce
};
await Merchant.updateOne(
{ shop },
{ nonce },
{ upsert: true }
).exec();
return `https://${shop}/admin/oauth/authorize?${stringify(query)}`;
}