0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

fix(connector): signature method for both Aliyun DM and SMS (#3920)

This commit is contained in:
Darcy Ye 2023-06-02 14:15:52 +08:00 committed by GitHub
parent f35d1cbb86
commit 64af2dc88e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 27 deletions

View file

@ -0,0 +1,6 @@
---
"@logto/connector-aliyun-dm": patch
"@logto/connector-aliyun-sms": patch
---
Fix Aliyun Direct Mail and Aliyun Short Message Service connectors' signature functions.

View file

@ -1,5 +1,6 @@
import { type Optional } from '@silverhand/essentials';
import { got } from 'got'; import { got } from 'got';
import { createHmac } from 'node:crypto'; import { createHmac, randomUUID } from 'node:crypto';
import type { PublicParameters } from './types.js'; import type { PublicParameters } from './types.js';
@ -7,26 +8,29 @@ import type { PublicParameters } from './types.js';
// https://help.aliyun.com/document_detail/29442.html // https://help.aliyun.com/document_detail/29442.html
const escaper = (string_: string) => const escaper = (string_: string) =>
encodeURIComponent(string_) encodeURIComponent(string_)
.replace(/\*/g, '%2A')
.replace(/'/g, '%27')
.replace(/!/g, '%21') .replace(/!/g, '%21')
.replace(/"/g, '%22') .replace(/"/g, '%22')
.replace(/'/g, '%27')
.replace(/\(/g, '%28') .replace(/\(/g, '%28')
.replace(/\)/g, '%29') .replace(/\)/g, '%29')
.replace(/\*/g, '%2A')
.replace(/\+/g, '%2B'); .replace(/\+/g, '%2B');
// Format date string to 'YYYY-MM-DDThh:mm:ssZ' format.
const formatDateString = (date: Date) => {
const rawString = date.toISOString();
return rawString.replace(/\.\d{3}Z$/, 'Z'); // Trim milliseconds.
};
export const getSignature = ( export const getSignature = (
parameters: Record<string, string>, parameters: Record<string, string>,
secret: string, secret: string,
method: string method: string
) => { ) => {
const canonicalizedQuery = Object.keys(parameters) const canonicalizedQuery = Object.entries(parameters)
.map((key) => { .map(([key, value]) => {
const value = parameters[key]; return `${escaper(key)}=${escaper(value)}`;
return value === undefined ? '' : `${escaper(key)}=${escaper(value)}`;
}) })
.filter(Boolean)
.slice() .slice()
.sort() .sort()
.join('&'); .join('&');
@ -41,11 +45,14 @@ export const request = async (
parameters: PublicParameters & Record<string, string>, parameters: PublicParameters & Record<string, string>,
accessKeySecret: string accessKeySecret: string
) => { ) => {
const finalParameters: Record<string, string> = { const finalParameters = Object.entries<Optional<string>>({
...parameters, ...parameters,
SignatureNonce: String(Math.random()), SignatureNonce: randomUUID(),
Timestamp: new Date().toISOString(), Timestamp: formatDateString(new Date()),
}; }).reduce<Record<string, string>>(
(result, [key, value]) => (value === undefined ? result : { ...result, [key]: value }),
{}
);
const signature = getSignature(finalParameters, accessKeySecret, 'POST'); const signature = getSignature(finalParameters, accessKeySecret, 'POST');
return got.post({ return got.post({

View file

@ -1,32 +1,35 @@
import { type Optional } from '@silverhand/essentials';
import { got } from 'got'; import { got } from 'got';
import { createHmac } from 'node:crypto'; import { createHmac, randomUUID } from 'node:crypto';
import type { PublicParameters } from './types.js'; import type { PublicParameters } from './types.js';
// Aliyun has special escape rules. // Aliyun has special escape rules.
// https://help.aliyun.com/document_detail/29442.html // https://help.aliyun.com/document_detail/29442.html
const escaper = (string_: string) => const escaper = (string_: string) =>
encodeURIComponent(string_) encodeURIComponent(string_)
.replace(/\*/g, '%2A')
.replace(/'/g, '%27')
.replace(/!/g, '%21') .replace(/!/g, '%21')
.replace(/"/g, '%22') .replace(/"/g, '%22')
.replace(/'/g, '%27')
.replace(/\(/g, '%28') .replace(/\(/g, '%28')
.replace(/\)/g, '%29') .replace(/\)/g, '%29')
.replace(/\*/g, '%2A')
.replace(/\+/g, '%2B'); .replace(/\+/g, '%2B');
// Format date string to 'YYYY-MM-DDThh:mm:ssZ' format.
const formatDateString = (date: Date) => {
const rawString = date.toISOString();
return rawString.replace(/\.\d{3}Z$/, 'Z'); // Trim milliseconds.
};
export const getSignature = ( export const getSignature = (
parameters: Record<string, string>, parameters: Record<string, string>,
secret: string, secret: string,
method: string method: string
) => { ) => {
const canonicalizedQuery = Object.keys(parameters) const canonicalizedQuery = Object.entries(parameters)
.map((key) => { .map(([key, value]) => {
const value = parameters[key]; return `${escaper(key)}=${escaper(value)}`;
return value === undefined ? '' : `${escaper(key)}=${escaper(value)}`;
}) })
.filter(Boolean)
.slice() .slice()
.sort() .sort()
.join('&'); .join('&');
@ -41,11 +44,14 @@ export const request = async (
parameters: PublicParameters & Record<string, string>, parameters: PublicParameters & Record<string, string>,
accessKeySecret: string accessKeySecret: string
) => { ) => {
const finalParameters: Record<string, string> = { const finalParameters = Object.entries<Optional<string>>({
...parameters, ...parameters,
SignatureNonce: String(Math.random()), SignatureNonce: randomUUID(),
Timestamp: new Date().toISOString(), Timestamp: formatDateString(new Date()),
}; }).reduce<Record<string, string>>(
(result, [key, value]) => (value === undefined ? result : { ...result, [key]: value }),
{}
);
const signature = getSignature(finalParameters, accessKeySecret, 'POST'); const signature = getSignature(finalParameters, accessKeySecret, 'POST');
return got.post({ return got.post({