-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
184 lines (155 loc) · 5.67 KB
/
index.js
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import crypto from 'node:crypto';
import express from 'express';
const env = {
PORT: 3000,
CAND_APP_CLIENT_ID: 'YOUR_CAND_APP_CLIENT_ID',
CAND_APP_REDIRECT_URI: 'http://localhost:3000/callback/canpass',
};
const db = {
codeVerifiers: {},
bots: {},
};
const app = express();
app.get('/', async (req, res) => {
const createCodeVerifier = () => btoa(String.fromCharCode(...new Uint8Array(crypto.getRandomValues(new Uint8Array(32)).buffer)));
const createCodeChallenge = async (verifier) => btoa(String.fromCharCode(...new Uint8Array(await crypto.subtle.digest("SHA-256", (new TextEncoder()).encode(verifier))))).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
const url = new URL('https://canpass.me/oauth2/authorize');
const codeVerifier = createCodeVerifier();
const codeChallenge = await createCodeChallenge(codeVerifier);
const nonce = `${Math.random()}`;
const state = {nonce};
const stateString = JSON.stringify(state);
const scopes = ['member:MOIM:product:read', 'member:MOIM:product:write', 'bot:MOIM:product:read', 'bot:MOIM:product:write', 'webhook:MOIM:product'];
const searchParams = new URLSearchParams({
response_type: 'code',
action: 'signin',
social_logins: 'all',
client_id: env.CAND_APP_CLIENT_ID,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
redirect_uri: env.CAND_APP_REDIRECT_URI,
scope: scopes.join(' '),
state: stateString,
// community_id should be included in the query
...req.query,
});
url.search = searchParams.toString();
const urlString = url.toString();
db.codeVerifiers[stateString] = codeVerifier;
res.redirect(urlString);
console.log('CANpass authorization endpoint url and used code verifier', urlString, codeVerifier);
});
app.get('/callback/canpass', async (req, res) => {
const {state: stateString, error, error_description, code} = req.query;
const codeVerifier = db.codeVerifiers[stateString];
delete db.codeVerifiers[stateString];
if (!codeVerifier) {
res.sendStatus(400);
return;
}
if (error) {
if (error !== 'access_denied') {
console.error(`Check the error: ${error} and ${error_description}`);
}
res.status(400).send(`Error: ${error} and ${error_description}`);
return;
}
const tokenRes = await fetch('https://canpass.me/oauth2/token', {
method: 'POST',
headers: {'content-type': 'application/x-www-form-urlencoded'},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: env.CAND_APP_CLIENT_ID,
code,
code_verifier: codeVerifier,
redirect_uri: env.CAND_APP_REDIRECT_URI,
}).toString(),
});
const tokenBody = await tokenRes.json();
console.log('CANpass token endpoint response status and body', tokenRes.status, tokenBody);
if (tokenRes.status !== 200) {
res.sendStatus(400);
return;
}
const meRes = await fetch('https://api.cand.xyz/me', {
headers: {
'content-type': 'application/json',
'authorization': `Bearer ${tokenBody.access_token}`,
'x-can-community-id': tokenBody.community_id,
},
});
const meBody = await meRes.json();
console.log('Moim GET /me response status and body', meRes.status, meBody);
if (tokenBody.bot_access_token) {
if (!db.bots[tokenBody.community_id]) {
const bot = {id: tokenBody.community_id, accessToken: tokenBody.bot_access_token};
db.bots[tokenBody.community_id] = bot;
console.log('New bot is created and stored', bot);
}
const productRes = await fetch('https://api.cand.xyz/products/', {
method: 'POST',
headers: {
'content-type': 'application/json',
'authorization': `Bearer ${tokenBody.bot_access_token}`,
'x-can-community-id': tokenBody.community_id,
},
body: JSON.stringify({
"name": "Example fancy product",
"type": "normal",
"isDisplayed": true,
"blocks": [
{
"type": "text",
"content": "sample text"
}
],
"images": {
"mobile": [
"https://dummyimage.com/350x350/000/fff&text=Product"
],
"web": [
"https://dummyimage.com/350x350/000/fff&text=Product"
]
},
"status": "onSale",
"price": 99,
"normalPrice": 100,
"originalPrice": 101,
"supplyPrice": 80,
"description": "description",
"sku": "sku#1",
"stockCount": 3,
"weight": 0
}),
});
const productBody = await productRes.json();
console.log('Moim POST /products response status and body', productRes.status, productBody);
}
res.send('See the console');
});
app.post('/callback/cand', async (req, res) => {
res.end();
const {module, type, context, payload} = req.body;
console.log('Webhook event given', module, type, context, payload);
switch (`${module}:${type}`) {
case 'MOIM:product.created': {
const botAccessToken = await db.bots[context.originCommunityId];
const productRes = await fetch(`https://api.cand.xyz/products/${payload.id}`, {
headers: {
'content-type': 'application/json',
'authorization': `Bearer ${botAccessToken}`,
'x-can-community-id': context.originCommunityId,
},
});
const productBody = await productRes.json();
console.log('Moim GET /products/${proudctId} response status and body', productRes.status, productBody);
break;
}
default:
console.warn(`No case statement available for ${module}:${type} event whose context is ${JSON.stringify(context)} and payload is ${JSON.stringify(payload)}`);
break;
}
});
app.listen(env.PORT, () => {
console.log(`Listening to port ${env.PORT}`);
});