-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSendOtp.js
More file actions
96 lines (85 loc) · 3.83 KB
/
Copy pathSendOtp.js
File metadata and controls
96 lines (85 loc) · 3.83 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
// <copyright file="SendOtp.js" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
'use strict';
const { app } = require('@azure/functions');
const crypto = require('crypto');
const {
dispatchOtp,
parseEnvelope,
decryptDeliveryContext,
contextToDispatch,
MODE,
} = require('./dispatch');
const { readConfig } = require('./config');
app.http('SendOtp', {
methods: ['POST'],
authLevel: 'anonymous', // Protected by platform authentication in Azure.
handler: async (request, context) => {
const started = Date.now();
const requestId = crypto.randomUUID();
let correlationId = requestId;
let evaluation = false;
let httpStatus = 500;
const respond = (status, jsonBody) => {
httpStatus = status;
return { status, jsonBody };
};
try {
const config = readConfig();
const clientRequestId = request.headers.get('x-ms-client-request-id') || requestId;
const headerCorrelationId = request.headers.get('x-ms-correlation-id') || null;
correlationId = headerCorrelationId || requestId;
let payload;
try {
payload = JSON.parse(await request.text());
} catch {
return respond(400, { error: 'bad_request', reason: 'invalid JSON body', requestId });
}
const parsed = parseEnvelope(payload);
if (parsed.error) {
return respond(400, { error: 'bad_request', reason: parsed.error, requestId });
}
const envelope = parsed.envelope;
correlationId = envelope.correlationId || headerCorrelationId || requestId;
evaluation = envelope.mode === MODE.EVALUATION;
let delivery;
let header;
try {
({ context: delivery, header } = await decryptDeliveryContext(
envelope.encryptedDeliveryContext, config));
} catch {
return respond(400, { error: 'decryption_failed', correlationId, requestId });
}
// Key ID is advisory after authenticated decryption.
if (config.expectedKeyId && config.expectedKeyId !== header.kid) {
context.warn('encryption_key_id_mismatch');
}
if (!delivery?.isComplete) {
return respond(400, { error: 'bad_request', reason: 'incomplete delivery context', correlationId, requestId });
}
// Evaluation proves decryption without resolving a provider or requiring provider config.
if (!evaluation) {
const dispatch = contextToDispatch(delivery, envelope, clientRequestId);
dispatch.correlationId = correlationId;
const result = await dispatchOtp(dispatch, { requestId, config }).catch(() => ({ httpStatus: 500 }));
if (result.httpStatus !== 200) {
return respond(result.httpStatus, { error: 'provider_delivery_failed', correlationId, requestId });
}
}
// Only a successful delivery (or validated evaluation) may echo the nonce and accepted.
return respond(200, { nonce: delivery.nonce, correlationId, providerStatus: 'accepted' });
} catch {
return respond(500, { error: 'delivery_failed', correlationId, requestId });
} finally {
const rawId = typeof correlationId === 'string' ? correlationId : JSON.stringify(correlationId);
context.log({
requestId,
correlationId: crypto.createHash('sha256').update(rawId).digest('hex').slice(0, 16),
httpStatus,
elapsedMs: Date.now() - started,
evaluation,
});
}
},
});