@unchainedshop/ticketing 4.3.4 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +168 -0
- package/lib/express.d.ts +0 -1
- package/lib/express.js +3 -4
- package/lib/fastify.d.ts +0 -1
- package/lib/fastify.js +3 -4
- package/lib/index.d.ts +6 -7
- package/lib/index.js +4 -5
- package/lib/magic-key.d.ts +0 -1
- package/lib/magic-key.js +0 -4
- package/lib/mobile-tickets/apple-handler-express.d.ts +1 -2
- package/lib/mobile-tickets/apple-handler-express.js +1 -8
- package/lib/mobile-tickets/apple-handler-fastify.d.ts +0 -1
- package/lib/mobile-tickets/apple-handler-fastify.js +1 -8
- package/lib/mobile-tickets/apple-wallet.d.ts +0 -1
- package/lib/mobile-tickets/apple-wallet.js +0 -1
- package/lib/mobile-tickets/google-handler-express.d.ts +1 -2
- package/lib/mobile-tickets/google-handler-express.js +0 -1
- package/lib/mobile-tickets/google-handler-fastify.d.ts +0 -1
- package/lib/mobile-tickets/google-handler-fastify.js +0 -1
- package/lib/module.d.ts +4 -5
- package/lib/module.js +3 -10
- package/lib/pdf-tickets/print-handler-express.d.ts +1 -2
- package/lib/pdf-tickets/print-handler-express.js +1 -2
- package/lib/pdf-tickets/print-handler-fastify.d.ts +0 -1
- package/lib/pdf-tickets/print-handler-fastify.js +1 -2
- package/lib/services.d.ts +2 -3
- package/lib/services.js +0 -1
- package/lib/template-registry.d.ts +10 -10
- package/lib/template-registry.js +5 -7
- package/lib/types.d.ts +3 -4
- package/lib/types.js +0 -1
- package/package.json +17 -15
- package/lib/express.d.ts.map +0 -1
- package/lib/express.js.map +0 -1
- package/lib/fastify.d.ts.map +0 -1
- package/lib/fastify.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/magic-key.d.ts.map +0 -1
- package/lib/magic-key.js.map +0 -1
- package/lib/mobile-tickets/apple-handler-express.d.ts.map +0 -1
- package/lib/mobile-tickets/apple-handler-express.js.map +0 -1
- package/lib/mobile-tickets/apple-handler-fastify.d.ts.map +0 -1
- package/lib/mobile-tickets/apple-handler-fastify.js.map +0 -1
- package/lib/mobile-tickets/apple-wallet.d.ts.map +0 -1
- package/lib/mobile-tickets/apple-wallet.js.map +0 -1
- package/lib/mobile-tickets/google-handler-express.d.ts.map +0 -1
- package/lib/mobile-tickets/google-handler-express.js.map +0 -1
- package/lib/mobile-tickets/google-handler-fastify.d.ts.map +0 -1
- package/lib/mobile-tickets/google-handler-fastify.js.map +0 -1
- package/lib/module.d.ts.map +0 -1
- package/lib/module.js.map +0 -1
- package/lib/pdf-tickets/print-handler-express.d.ts.map +0 -1
- package/lib/pdf-tickets/print-handler-express.js.map +0 -1
- package/lib/pdf-tickets/print-handler-fastify.d.ts.map +0 -1
- package/lib/pdf-tickets/print-handler-fastify.js.map +0 -1
- package/lib/services.d.ts.map +0 -1
- package/lib/services.js.map +0 -1
- package/lib/template-registry.d.ts.map +0 -1
- package/lib/template-registry.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- package/readme.md +0 -118
- package/src/express.ts +0 -35
- package/src/fastify.ts +0 -30
- package/src/index.ts +0 -66
- package/src/magic-key.ts +0 -43
- package/src/mobile-tickets/apple-handler-express.ts +0 -237
- package/src/mobile-tickets/apple-handler-fastify.ts +0 -279
- package/src/mobile-tickets/apple-wallet.ts +0 -31
- package/src/mobile-tickets/google-handler-express.ts +0 -57
- package/src/mobile-tickets/google-handler-fastify.ts +0 -69
- package/src/module.ts +0 -251
- package/src/pdf-tickets/print-handler-express.ts +0 -34
- package/src/pdf-tickets/print-handler-fastify.ts +0 -43
- package/src/services.ts +0 -35
- package/src/template-registry.ts +0 -48
- package/src/types.ts +0 -8
- package/tsconfig.json +0 -10
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
-
import type { Request, Response } from 'express';
|
|
3
|
-
import { TicketingAPI } from '../types.js';
|
|
4
|
-
import { getFileAdapter } from '@unchainedshop/core-files';
|
|
5
|
-
|
|
6
|
-
const logger = createLogger('unchained:apple-wallet-webservice');
|
|
7
|
-
|
|
8
|
-
const isAuthenticationTokenCorrect = (req, authenticationToken) => {
|
|
9
|
-
const expectedAuthorizationValue = `ApplePass ${authenticationToken}`;
|
|
10
|
-
return req.headers.authorization === expectedAuthorizationValue;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const appleWalletHandler = async (req: Request & { unchainedContext: TicketingAPI }, res: Response) => {
|
|
14
|
-
const resolvedContext = req.unchainedContext;
|
|
15
|
-
const { modules } = resolvedContext;
|
|
16
|
-
logger.info(`${req.path} (${JSON.stringify(req.query)})`);
|
|
17
|
-
|
|
18
|
-
if (req.path.startsWith('/download/')) {
|
|
19
|
-
try {
|
|
20
|
-
const [, , passFileName] = req.path.split('/');
|
|
21
|
-
const [tokenId] = passFileName.split('.pkpass');
|
|
22
|
-
|
|
23
|
-
if (!tokenId) {
|
|
24
|
-
res.status(404).end();
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const token = await modules.warehousing.findToken({
|
|
29
|
-
tokenId,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (!token) {
|
|
33
|
-
res.status(404).send('Token not found');
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const { hash } = req.query;
|
|
38
|
-
const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
|
|
39
|
-
if (!hash || hash !== correctHash) {
|
|
40
|
-
res.status(403).send('Token hash invalid for current owner');
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const passFile = await modules.passes.upsertAppleWalletPass(token, resolvedContext);
|
|
45
|
-
|
|
46
|
-
const fileUploadAdapter = getFileAdapter();
|
|
47
|
-
const signedUrl = await fileUploadAdapter.createDownloadURL(passFile);
|
|
48
|
-
const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
|
|
49
|
-
|
|
50
|
-
if (!url) {
|
|
51
|
-
res.status(500);
|
|
52
|
-
res.send({
|
|
53
|
-
success: false,
|
|
54
|
-
message: 'Could not create download URL',
|
|
55
|
-
name: 'URL_SIGNING_FAILED',
|
|
56
|
-
});
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const response = await fetch(url);
|
|
61
|
-
const data = await response.arrayBuffer();
|
|
62
|
-
const uint8View = new Uint8Array(data);
|
|
63
|
-
|
|
64
|
-
res.status(200);
|
|
65
|
-
res.setHeader('Content-Type', 'application/vnd.apple.pkpass');
|
|
66
|
-
res.setHeader('Content-Disposition', `attachment; filename=${tokenId}.pkpass`);
|
|
67
|
-
res.send(uint8View);
|
|
68
|
-
return;
|
|
69
|
-
} catch (e) {
|
|
70
|
-
logger.error(e);
|
|
71
|
-
}
|
|
72
|
-
res.status(500).end();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const [, apiVersion, endpoint, ...pathComponents] = req.path.split("/"); /* eslint-disable-line */
|
|
77
|
-
if (endpoint === 'devices') {
|
|
78
|
-
if (req.method === 'POST') {
|
|
79
|
-
// Register Device
|
|
80
|
-
const [deviceLibraryIdentifier, , passTypeIdentifier, serialNumber] = pathComponents;
|
|
81
|
-
const { pushToken } = req.body;
|
|
82
|
-
|
|
83
|
-
const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
|
|
84
|
-
|
|
85
|
-
if (pass) {
|
|
86
|
-
if (
|
|
87
|
-
!isAuthenticationTokenCorrect(
|
|
88
|
-
req,
|
|
89
|
-
(pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
|
|
90
|
-
)
|
|
91
|
-
) {
|
|
92
|
-
res.status(401).end();
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const newRegistration = await modules.passes.registerDeviceForAppleWalletPass(
|
|
97
|
-
passTypeIdentifier,
|
|
98
|
-
serialNumber,
|
|
99
|
-
{
|
|
100
|
-
deviceLibraryIdentifier,
|
|
101
|
-
pushToken,
|
|
102
|
-
},
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
if (!newRegistration) {
|
|
106
|
-
res.status(200).end();
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
res.status(201).end();
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
} else if (req.method === 'GET') {
|
|
114
|
-
// Get the List of Updatable Passes
|
|
115
|
-
const lastUpdated = new Date();
|
|
116
|
-
const [deviceLibraryIdentifier, , passTypeIdentifier] = pathComponents;
|
|
117
|
-
const passesUpdatedSince =
|
|
118
|
-
req.query?.passesUpdatedSince && new Date(req.query.passesUpdatedSince as string);
|
|
119
|
-
|
|
120
|
-
const passes = await modules.passes.findUpdatedAppleWalletPasses(
|
|
121
|
-
passTypeIdentifier,
|
|
122
|
-
deviceLibraryIdentifier,
|
|
123
|
-
passesUpdatedSince,
|
|
124
|
-
);
|
|
125
|
-
const serialNumbers = passes.map((t) => t.meta?.serialNumber).filter(Boolean) as string[];
|
|
126
|
-
|
|
127
|
-
if (serialNumbers?.length) {
|
|
128
|
-
res.status(200).send({ serialNumbers, lastUpdated });
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
res.status(204).end();
|
|
133
|
-
return;
|
|
134
|
-
} else if (req.method === 'DELETE') {
|
|
135
|
-
// Unregister Device
|
|
136
|
-
const [deviceLibraryIdentifier, , passTypeIdentifier, serialNumber] = pathComponents;
|
|
137
|
-
|
|
138
|
-
const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
|
|
139
|
-
|
|
140
|
-
if (pass) {
|
|
141
|
-
if (
|
|
142
|
-
!isAuthenticationTokenCorrect(
|
|
143
|
-
req,
|
|
144
|
-
(pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
|
|
145
|
-
)
|
|
146
|
-
) {
|
|
147
|
-
res.status(401).end();
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
await modules.passes.unregisterDeviceForAppleWalletPass(
|
|
152
|
-
passTypeIdentifier,
|
|
153
|
-
serialNumber,
|
|
154
|
-
deviceLibraryIdentifier,
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
// Unregistered
|
|
158
|
-
res.status(200).end();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
} else if (endpoint === 'log') {
|
|
163
|
-
// Log Mesage
|
|
164
|
-
const { logs } = req.body;
|
|
165
|
-
logs?.forEach((log) => {
|
|
166
|
-
if (typeof log === 'string') {
|
|
167
|
-
logger.info(log);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
res.status(200).end();
|
|
171
|
-
return;
|
|
172
|
-
} else if (endpoint === 'passes') {
|
|
173
|
-
if (req.method === 'GET') {
|
|
174
|
-
// Get an updated Pass
|
|
175
|
-
const [passTypeIdentifier, serialNumber] = pathComponents;
|
|
176
|
-
|
|
177
|
-
const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
|
|
178
|
-
|
|
179
|
-
if (!pass) {
|
|
180
|
-
res.status(404).end();
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
!isAuthenticationTokenCorrect(
|
|
186
|
-
req,
|
|
187
|
-
(pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
|
|
188
|
-
)
|
|
189
|
-
) {
|
|
190
|
-
res.status(401).end();
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const { updated, created } = pass;
|
|
195
|
-
|
|
196
|
-
const lastModifiedDate = new Date(updated || created);
|
|
197
|
-
lastModifiedDate.setMilliseconds(0);
|
|
198
|
-
|
|
199
|
-
const ifModifiedSinceDate = new Date(req.header('if-modified-since')!);
|
|
200
|
-
ifModifiedSinceDate.setMilliseconds(0);
|
|
201
|
-
|
|
202
|
-
if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
|
|
203
|
-
res.status(304).end();
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const fileUploadAdapter = getFileAdapter();
|
|
208
|
-
const signedUrl = await fileUploadAdapter.createDownloadURL(pass);
|
|
209
|
-
const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
|
|
210
|
-
|
|
211
|
-
if (!url) {
|
|
212
|
-
res.status(500);
|
|
213
|
-
res.send({
|
|
214
|
-
success: false,
|
|
215
|
-
message: 'Could not create download URL',
|
|
216
|
-
name: 'URL_SIGNING_FAILED',
|
|
217
|
-
});
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const result = await fetch(url);
|
|
222
|
-
const data = await result.arrayBuffer();
|
|
223
|
-
const uint8View = new Uint8Array(data);
|
|
224
|
-
|
|
225
|
-
res.writeHead(200, {
|
|
226
|
-
'Content-Type': 'application/vnd.apple.pkpass',
|
|
227
|
-
'Last-Modified': lastModifiedDate.toUTCString(),
|
|
228
|
-
});
|
|
229
|
-
res.send(uint8View);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
res.status(404).end();
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
export default appleWalletHandler;
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
-
import { TicketingAPI } from '../types.js';
|
|
3
|
-
import { getFileAdapter } from '@unchainedshop/core-files';
|
|
4
|
-
import type { FastifyRequest, RouteHandlerMethod } from 'fastify';
|
|
5
|
-
|
|
6
|
-
const logger = createLogger('unchained:apple-wallet-webservice');
|
|
7
|
-
|
|
8
|
-
const isAuthenticationTokenCorrect = (req, authenticationToken) => {
|
|
9
|
-
const expectedAuthorizationValue = `ApplePass ${authenticationToken}`;
|
|
10
|
-
return req.headers.authorization === expectedAuthorizationValue;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const appleWalletHandler: RouteHandlerMethod = async (
|
|
14
|
-
req: FastifyRequest & {
|
|
15
|
-
unchainedContext: TicketingAPI;
|
|
16
|
-
},
|
|
17
|
-
reply,
|
|
18
|
-
) => {
|
|
19
|
-
const resolvedContext = req.unchainedContext;
|
|
20
|
-
const { modules } = resolvedContext;
|
|
21
|
-
|
|
22
|
-
const path = req.url;
|
|
23
|
-
|
|
24
|
-
logger.info(`${path} (${JSON.stringify(req.query)})`);
|
|
25
|
-
|
|
26
|
-
if (path.startsWith('/download/')) {
|
|
27
|
-
try {
|
|
28
|
-
const [, , passFileName] = path.split('/');
|
|
29
|
-
const [tokenId] = passFileName.split('.pkpass');
|
|
30
|
-
|
|
31
|
-
if (!tokenId) {
|
|
32
|
-
reply.status(404);
|
|
33
|
-
return reply.send();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const token = await modules.warehousing.findToken({
|
|
37
|
-
tokenId,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (!token) {
|
|
41
|
-
logger.error('Token not found', { tokenId });
|
|
42
|
-
reply.status(404);
|
|
43
|
-
return reply.send();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const { hash } = req.query as Record<string, string>;
|
|
47
|
-
const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
|
|
48
|
-
if (!hash || hash !== correctHash) {
|
|
49
|
-
logger.error('Token hash invalid for current owner', { tokenId });
|
|
50
|
-
reply.status(403);
|
|
51
|
-
return reply.send({
|
|
52
|
-
success: false,
|
|
53
|
-
message: 'Token hash invalid for current owner',
|
|
54
|
-
name: 'HASH_MISMATCH',
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const passFile = await modules.passes.upsertAppleWalletPass(token, resolvedContext);
|
|
59
|
-
|
|
60
|
-
const fileUploadAdapter = getFileAdapter();
|
|
61
|
-
const signedUrl = await fileUploadAdapter.createDownloadURL(passFile);
|
|
62
|
-
const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
|
|
63
|
-
|
|
64
|
-
if (!url) {
|
|
65
|
-
reply.status(500);
|
|
66
|
-
return reply.send({
|
|
67
|
-
success: false,
|
|
68
|
-
message: 'Could not create download URL',
|
|
69
|
-
name: 'URL_SIGNING_FAILED',
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const response = await fetch(url);
|
|
74
|
-
const data = await response.arrayBuffer();
|
|
75
|
-
const uint8View = new Uint8Array(data);
|
|
76
|
-
|
|
77
|
-
reply.status(200);
|
|
78
|
-
reply.header('content-type', 'application/vnd.apple.pkpass');
|
|
79
|
-
reply.header('Content-Disposition', `attachment; filename=${tokenId}.pkpass`);
|
|
80
|
-
return reply.send(uint8View);
|
|
81
|
-
} catch (e) {
|
|
82
|
-
logger.error(e);
|
|
83
|
-
reply.status(500);
|
|
84
|
-
return reply.send({
|
|
85
|
-
success: false,
|
|
86
|
-
message: 'Error generating pass',
|
|
87
|
-
name: 'PASS_GENERATION_ERROR',
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const [, apiVersion, endpoint, ...pathComponents] = path.split("/"); /* eslint-disable-line */
|
|
93
|
-
if (endpoint === 'devices') {
|
|
94
|
-
if (req.method === 'POST') {
|
|
95
|
-
// Register Device
|
|
96
|
-
const [deviceLibraryIdentifier, , passTypeIdentifier, serialNumber] = pathComponents;
|
|
97
|
-
const { pushToken } = req.body as Record<string, any>;
|
|
98
|
-
|
|
99
|
-
const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
|
|
100
|
-
|
|
101
|
-
if (pass) {
|
|
102
|
-
if (
|
|
103
|
-
!isAuthenticationTokenCorrect(
|
|
104
|
-
req,
|
|
105
|
-
(pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
|
|
106
|
-
)
|
|
107
|
-
) {
|
|
108
|
-
logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
|
|
109
|
-
reply.status(401);
|
|
110
|
-
return reply.send();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const newRegistration = await modules.passes.registerDeviceForAppleWalletPass(
|
|
114
|
-
passTypeIdentifier,
|
|
115
|
-
serialNumber,
|
|
116
|
-
{
|
|
117
|
-
deviceLibraryIdentifier,
|
|
118
|
-
pushToken,
|
|
119
|
-
},
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
if (!newRegistration) {
|
|
123
|
-
reply.status(200);
|
|
124
|
-
return reply.send({
|
|
125
|
-
success: true,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
reply.status(201);
|
|
130
|
-
return reply.send({
|
|
131
|
-
success: true,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
} else if (req.method === 'GET') {
|
|
135
|
-
// Get the List of Updatable Passes
|
|
136
|
-
const lastUpdated = new Date();
|
|
137
|
-
const [deviceLibraryIdentifier, , passTypeIdentifier] = pathComponents;
|
|
138
|
-
const passesUpdatedSince =
|
|
139
|
-
(req.query as Record<string, string>)?.passesUpdatedSince &&
|
|
140
|
-
new Date((req.query as Record<string, string>).passesUpdatedSince as string);
|
|
141
|
-
|
|
142
|
-
const passes = await modules.passes.findUpdatedAppleWalletPasses(
|
|
143
|
-
passTypeIdentifier,
|
|
144
|
-
deviceLibraryIdentifier,
|
|
145
|
-
passesUpdatedSince,
|
|
146
|
-
);
|
|
147
|
-
const serialNumbers = passes.map((t) => t.meta?.serialNumber).filter(Boolean) as string[];
|
|
148
|
-
|
|
149
|
-
if (serialNumbers?.length) {
|
|
150
|
-
reply.status(200);
|
|
151
|
-
return reply.send({
|
|
152
|
-
success: true,
|
|
153
|
-
serialNumbers,
|
|
154
|
-
lastUpdated,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
reply.status(204);
|
|
159
|
-
return reply.send({ success: true });
|
|
160
|
-
} else if (req.method === 'DELETE') {
|
|
161
|
-
// Unregister Device
|
|
162
|
-
const [deviceLibraryIdentifier, , passTypeIdentifier, serialNumber] = pathComponents;
|
|
163
|
-
|
|
164
|
-
const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
|
|
165
|
-
|
|
166
|
-
if (pass) {
|
|
167
|
-
if (
|
|
168
|
-
!isAuthenticationTokenCorrect(
|
|
169
|
-
req,
|
|
170
|
-
(pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
|
|
171
|
-
)
|
|
172
|
-
) {
|
|
173
|
-
logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
|
|
174
|
-
reply.status(401);
|
|
175
|
-
return reply.send({
|
|
176
|
-
success: false,
|
|
177
|
-
message: 'Unauthorized',
|
|
178
|
-
name: 'UNAUTHORIZED',
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
await modules.passes.unregisterDeviceForAppleWalletPass(
|
|
183
|
-
passTypeIdentifier,
|
|
184
|
-
serialNumber,
|
|
185
|
-
deviceLibraryIdentifier,
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
// Unregistered
|
|
189
|
-
reply.status(200);
|
|
190
|
-
return reply.send({
|
|
191
|
-
success: true,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
} else if (endpoint === 'log') {
|
|
196
|
-
// Log Mesage
|
|
197
|
-
const { logs } = req.body as Record<string, any>;
|
|
198
|
-
logs?.forEach((log) => {
|
|
199
|
-
if (typeof log === 'string') {
|
|
200
|
-
logger.info(log);
|
|
201
|
-
}
|
|
202
|
-
});
|
|
203
|
-
reply.status(200);
|
|
204
|
-
return reply.send({
|
|
205
|
-
success: true,
|
|
206
|
-
});
|
|
207
|
-
} else if (endpoint === 'passes') {
|
|
208
|
-
if (req.method === 'GET') {
|
|
209
|
-
// Get an updated Pass
|
|
210
|
-
const [passTypeIdentifier, serialNumber] = pathComponents;
|
|
211
|
-
|
|
212
|
-
const pass = await modules.passes.findAppleWalletPass(passTypeIdentifier, serialNumber);
|
|
213
|
-
|
|
214
|
-
if (!pass) {
|
|
215
|
-
reply.status(404);
|
|
216
|
-
return reply.send();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
if (
|
|
220
|
-
!isAuthenticationTokenCorrect(
|
|
221
|
-
req,
|
|
222
|
-
(pass.meta?.rawData as any)?._id || (pass.meta?.rawData as any)?.tokenId,
|
|
223
|
-
)
|
|
224
|
-
) {
|
|
225
|
-
logger.error('Unauthorized', { passTypeIdentifier, serialNumber });
|
|
226
|
-
reply.status(401);
|
|
227
|
-
return reply.send({
|
|
228
|
-
success: false,
|
|
229
|
-
message: 'Unauthorized',
|
|
230
|
-
name: 'UNAUTHORIZED',
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const { updated, created } = pass;
|
|
235
|
-
|
|
236
|
-
const lastModifiedDate = new Date(updated || created);
|
|
237
|
-
lastModifiedDate.setMilliseconds(0);
|
|
238
|
-
|
|
239
|
-
const ifModifiedSinceDate = new Date(req.headers['if-modified-since']!);
|
|
240
|
-
ifModifiedSinceDate.setMilliseconds(0);
|
|
241
|
-
|
|
242
|
-
if (ifModifiedSinceDate.getTime() >= lastModifiedDate.getTime()) {
|
|
243
|
-
reply.status(304);
|
|
244
|
-
return reply.send({
|
|
245
|
-
success: true,
|
|
246
|
-
message: 'Not modified',
|
|
247
|
-
name: 'NOT_MODIFIED',
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const fileUploadAdapter = getFileAdapter();
|
|
252
|
-
const signedUrl = await fileUploadAdapter.createDownloadURL(pass);
|
|
253
|
-
const url = signedUrl && (await modules.files.normalizeUrl(signedUrl, {}));
|
|
254
|
-
|
|
255
|
-
if (!url) {
|
|
256
|
-
reply.status(500);
|
|
257
|
-
return reply.send({
|
|
258
|
-
success: false,
|
|
259
|
-
message: 'Could not create download URL',
|
|
260
|
-
name: 'URL_SIGNING_FAILED',
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const result = await fetch(url);
|
|
265
|
-
const data = await result.arrayBuffer();
|
|
266
|
-
const uint8View = new Uint8Array(data);
|
|
267
|
-
|
|
268
|
-
reply.status(200);
|
|
269
|
-
reply.header('content-type', 'application/vnd.apple.pkpass');
|
|
270
|
-
reply.header('last-modified', lastModifiedDate.toUTCString());
|
|
271
|
-
return reply.send(uint8View);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
reply.status(404);
|
|
276
|
-
return reply.send();
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
export default appleWalletHandler;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import apn from '@parse/node-apn';
|
|
2
|
-
import { Readable } from 'node:stream';
|
|
3
|
-
|
|
4
|
-
export const pushToApplePushNotificationService = async (deviceTokens) => {
|
|
5
|
-
const apnProvider = new apn.Provider({
|
|
6
|
-
cert: process.env.PASS_CERTIFICATE_PATH,
|
|
7
|
-
key: process.env.PASS_CERTIFICATE_PATH,
|
|
8
|
-
passphrase: process.env.PASS_CERTIFICATE_SECRET,
|
|
9
|
-
production: true,
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
const note = new apn.Notification({});
|
|
13
|
-
return apnProvider.send(note, deviceTokens);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export const buildPassBinary = async (
|
|
17
|
-
tokenSerialNumber: string,
|
|
18
|
-
pass: {
|
|
19
|
-
serialNumber: string;
|
|
20
|
-
asBuffer: () => Promise<Buffer>;
|
|
21
|
-
},
|
|
22
|
-
) => {
|
|
23
|
-
const passBuffer = await pass.asBuffer();
|
|
24
|
-
const rawFile = {
|
|
25
|
-
_id: pass.serialNumber,
|
|
26
|
-
filename: `${tokenSerialNumber}-${new Date().getTime()}.pkpass`,
|
|
27
|
-
createReadStream: () => Readable.from(passBuffer),
|
|
28
|
-
mimetype: 'application/vnd.apple.pkpass',
|
|
29
|
-
};
|
|
30
|
-
return rawFile;
|
|
31
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
-
import type { Request, Response } from 'express';
|
|
3
|
-
import { TicketingAPI } from '../types.js';
|
|
4
|
-
|
|
5
|
-
const logger = createLogger('unchained:google-wallet-webservice');
|
|
6
|
-
|
|
7
|
-
export const googleWalletHandler = async (
|
|
8
|
-
req: Request & { unchainedContext: TicketingAPI },
|
|
9
|
-
res: Response,
|
|
10
|
-
) => {
|
|
11
|
-
const resolvedContext = req.unchainedContext;
|
|
12
|
-
const { modules } = resolvedContext;
|
|
13
|
-
logger.info(`${req.path} (${JSON.stringify(req.query)})`);
|
|
14
|
-
|
|
15
|
-
if (req.path.includes('/download/')) {
|
|
16
|
-
try {
|
|
17
|
-
const { tokenId } = req.params as { tokenId: string };
|
|
18
|
-
const { hash } = req.query as { hash?: string };
|
|
19
|
-
|
|
20
|
-
if (!tokenId) {
|
|
21
|
-
res.status(404).end();
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const token = await modules.warehousing.findToken({
|
|
26
|
-
tokenId,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
if (!token) {
|
|
30
|
-
res.status(404).send('Token not found');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
|
|
35
|
-
if (!hash || hash !== correctHash) {
|
|
36
|
-
res.status(403).send('Token hash invalid for current owner');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const pass = await modules.passes.upsertGoogleWalletPass(token, resolvedContext);
|
|
41
|
-
|
|
42
|
-
if (!pass) {
|
|
43
|
-
throw new Error('Could not create Google Wallet pass');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
res.redirect(await pass.asURL());
|
|
47
|
-
return;
|
|
48
|
-
} catch (e) {
|
|
49
|
-
logger.error(e);
|
|
50
|
-
}
|
|
51
|
-
res.status(500).end();
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
res.status(404).end();
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
export default googleWalletHandler;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { createLogger } from '@unchainedshop/logger';
|
|
2
|
-
import { TicketingAPI } from '../types.js';
|
|
3
|
-
import type { FastifyRequest, RouteHandlerMethod } from 'fastify';
|
|
4
|
-
|
|
5
|
-
const logger = createLogger('unchained:google-wallet-webservice');
|
|
6
|
-
|
|
7
|
-
const googleWalletHandler: RouteHandlerMethod = async (
|
|
8
|
-
req: FastifyRequest & {
|
|
9
|
-
unchainedContext: TicketingAPI;
|
|
10
|
-
},
|
|
11
|
-
reply,
|
|
12
|
-
) => {
|
|
13
|
-
const resolvedContext = req.unchainedContext;
|
|
14
|
-
const { modules } = resolvedContext;
|
|
15
|
-
|
|
16
|
-
const path = req.url;
|
|
17
|
-
|
|
18
|
-
logger.info(`${path} (${JSON.stringify(req.query)})`);
|
|
19
|
-
if (path.includes('/download/')) {
|
|
20
|
-
try {
|
|
21
|
-
const { tokenId } = req.params as { tokenId: string };
|
|
22
|
-
const { hash } = req.query as { hash?: string };
|
|
23
|
-
if (!tokenId) {
|
|
24
|
-
reply.status(404);
|
|
25
|
-
return reply.send();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const token = await modules.warehousing.findToken({
|
|
29
|
-
tokenId,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (!token) {
|
|
33
|
-
logger.error('Token not found', { tokenId });
|
|
34
|
-
reply.status(404);
|
|
35
|
-
return reply.send('Token not found');
|
|
36
|
-
}
|
|
37
|
-
const correctHash = await modules.warehousing.buildAccessKeyForToken(tokenId);
|
|
38
|
-
if (!hash || hash !== correctHash) {
|
|
39
|
-
logger.error('Token hash invalid for current owner', { tokenId });
|
|
40
|
-
reply.status(403);
|
|
41
|
-
return reply.send({
|
|
42
|
-
success: false,
|
|
43
|
-
message: 'Token hash invalid for current owner',
|
|
44
|
-
name: 'HASH_MISMATCH',
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const pass = await modules.passes.upsertGoogleWalletPass(token, resolvedContext);
|
|
49
|
-
|
|
50
|
-
if (!pass) {
|
|
51
|
-
throw new Error('Could not create Google Wallet pass');
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return reply.redirect(await pass.asURL());
|
|
55
|
-
} catch (e) {
|
|
56
|
-
logger.error(e);
|
|
57
|
-
reply.status(500);
|
|
58
|
-
return reply.send({
|
|
59
|
-
success: false,
|
|
60
|
-
message: 'Error generating pass',
|
|
61
|
-
name: 'PASS_GENERATION_ERROR',
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
reply.status(404);
|
|
66
|
-
return reply.send();
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
export default googleWalletHandler;
|