feat: 添加华为云API网关SDK并重构认证逻辑
重构服务器端认证逻辑,使用华为云API网关SDK替代原有实现 新增自动获取授权码功能,当用户未输入时自动获取 添加SDK相关测试文件和许可证
This commit is contained in:
@@ -181,13 +181,25 @@ async function initializeSDK() {
|
|||||||
try {
|
try {
|
||||||
const serverAddress = elements.serverAddress.value.trim();
|
const serverAddress = elements.serverAddress.value.trim();
|
||||||
const robotId = elements.robotId.value.trim();
|
const robotId = elements.robotId.value.trim();
|
||||||
const onceCode = elements.onceCode.value.trim();
|
let onceCode = elements.onceCode.value.trim();
|
||||||
|
|
||||||
if (!serverAddress) {
|
if (!serverAddress) {
|
||||||
logger.error('请输入服务器地址');
|
logger.error('请输入服务器地址');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!onceCode) {
|
||||||
|
logger.info('授权码为空,正在自动获取...');
|
||||||
|
try {
|
||||||
|
onceCode = await fetchOnceCode();
|
||||||
|
elements.onceCode.value = onceCode;
|
||||||
|
logger.info('授权码自动获取成功');
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('授权码自动获取失败', err.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('正在初始化 SDK...');
|
logger.info('正在初始化 SDK...');
|
||||||
|
|
||||||
const isSupported = await HwICSUiSdk.checkBrowserSupport(true);
|
const isSupported = await HwICSUiSdk.checkBrowserSupport(true);
|
||||||
|
|||||||
24
server/APIGW-javascript-sdk-2.0.6/licenses/license-crypto-js
Normal file
24
server/APIGW-javascript-sdk-2.0.6/licenses/license-crypto-js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# License
|
||||||
|
|
||||||
|
[The MIT License (MIT)](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2009-2013 Jeff Mott
|
||||||
|
Copyright (c) 2013-2016 Evan Vosberg
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
20
server/APIGW-javascript-sdk-2.0.6/licenses/license-node
Normal file
20
server/APIGW-javascript-sdk-2.0.6/licenses/license-node
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
// copy of this software and associated documentation files (the
|
||||||
|
// "Software"), to deal in the Software without restriction, including
|
||||||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
// persons to whom the Software is furnished to do so, subject to the
|
||||||
|
// following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included
|
||||||
|
// in all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
37
server/APIGW-javascript-sdk-2.0.6/node_demo.js
Normal file
37
server/APIGW-javascript-sdk-2.0.6/node_demo.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
var signer = require('./signer');
|
||||||
|
var https = require("https");
|
||||||
|
var sig = new signer.Signer();
|
||||||
|
//Set the AK/SK to sign and authenticate the request.
|
||||||
|
// 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;
|
||||||
|
// 本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。
|
||||||
|
sig.Key = process.env.HUAWEICLOUD_SDK_AK;
|
||||||
|
sig.Secret = process.env.HUAWEICLOUD_SDK_SK;
|
||||||
|
|
||||||
|
//The following example shows how to set the request URL and parameters to query a VPC list.
|
||||||
|
//Specify a request method, such as GET, PUT, POST, DELETE, HEAD, and PATCH.
|
||||||
|
//Set request host.
|
||||||
|
//Set request URI.
|
||||||
|
//Set parameters for the request URL.
|
||||||
|
var r = new signer.HttpRequest("GET", "endpoint.example.com/v1/77b6a44cba5143ab91d13ab9a8ff44fd/vpcs?limie=1");
|
||||||
|
//Add header parameters, for example, x-domain-id for invoking a global service and x-project-id for invoking a project-level service.
|
||||||
|
r.headers = {"Content-Type": "application/json"};
|
||||||
|
//Add a body if you have specified the PUT or POST method. Special characters, such as the double quotation mark ("), contained in the body must be escaped.
|
||||||
|
r.body = '';
|
||||||
|
|
||||||
|
var opt = sig.Sign(r);
|
||||||
|
console.log(opt.headers["X-Sdk-Date"]);
|
||||||
|
console.log(opt.headers["Authorization"]);
|
||||||
|
|
||||||
|
var req = https.request(opt, function (res) {
|
||||||
|
console.log(res.statusCode);
|
||||||
|
console.log('headers:', JSON.stringify(res.headers));
|
||||||
|
res.on("data", function (chunk) {
|
||||||
|
console.log(chunk.toString())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on("error", function (err) {
|
||||||
|
console.log(err.message)
|
||||||
|
});
|
||||||
|
req.write(r.body);
|
||||||
|
req.end();
|
||||||
374
server/APIGW-javascript-sdk-2.0.6/signer.js
Normal file
374
server/APIGW-javascript-sdk-2.0.6/signer.js
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
// HWS API Gateway Signature
|
||||||
|
(function (root, factory) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/*global define*/
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
// AMD
|
||||||
|
define(['CryptoJS'], function (CryptoJS) {
|
||||||
|
var crypto_wrapper = {
|
||||||
|
hmacsha256: function (keyByte, message) {
|
||||||
|
return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex)
|
||||||
|
},
|
||||||
|
HexEncodeSHA256Hash: function (body) {
|
||||||
|
return CryptoJS.SHA256(body)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return factory(crypto_wrapper)
|
||||||
|
});
|
||||||
|
} else if (typeof wx === 'object') {
|
||||||
|
// wechat
|
||||||
|
var CryptoJS = require('./js/hmac-sha256.js');
|
||||||
|
var crypto_wrapper = {
|
||||||
|
hmacsha256: function (keyByte, message) {
|
||||||
|
return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex)
|
||||||
|
},
|
||||||
|
HexEncodeSHA256Hash: function (body) {
|
||||||
|
return CryptoJS.SHA256(body)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module.exports = factory(crypto_wrapper);
|
||||||
|
} else if (typeof module === 'object' && module.exports) {
|
||||||
|
// Node
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var crypto_wrapper = {
|
||||||
|
hmacsha256: function (keyByte, message) {
|
||||||
|
return crypto.createHmac('SHA256', keyByte).update(message).digest().toString('hex')
|
||||||
|
},
|
||||||
|
HexEncodeSHA256Hash: function (body) {
|
||||||
|
return crypto.createHash('SHA256').update(body).digest().toString('hex')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module.exports = factory(crypto_wrapper);
|
||||||
|
} else {
|
||||||
|
// Browser
|
||||||
|
var CryptoJS = root.CryptoJS
|
||||||
|
var crypto_wrapper = {
|
||||||
|
hmacsha256: function (keyByte, message) {
|
||||||
|
return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex)
|
||||||
|
},
|
||||||
|
HexEncodeSHA256Hash: function (body) {
|
||||||
|
return CryptoJS.SHA256(body)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
root.signer = factory(crypto_wrapper);
|
||||||
|
}
|
||||||
|
}(this, function (crypto_wrapper) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Algorithm = "SDK-HMAC-SHA256";
|
||||||
|
var HeaderXDate = "X-Sdk-Date";
|
||||||
|
var HeaderAuthorization = "Authorization";
|
||||||
|
var HeaderContentSha256 = "x-sdk-content-sha256";
|
||||||
|
|
||||||
|
const hexTable = new Array(256);
|
||||||
|
for (var i = 0; i < 256; ++i)
|
||||||
|
hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
|
||||||
|
|
||||||
|
const noEscape = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, // 32 - 47
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 80 - 95
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0 // 112 - 127
|
||||||
|
];
|
||||||
|
|
||||||
|
// function urlEncode is based on https://github.com/nodejs/node/blob/master/lib/querystring.js
|
||||||
|
// Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
function urlEncode(str) {
|
||||||
|
if (typeof str !== 'string') {
|
||||||
|
if (typeof str === 'object')
|
||||||
|
str = String(str);
|
||||||
|
else
|
||||||
|
str += '';
|
||||||
|
}
|
||||||
|
var out = '';
|
||||||
|
var lastPos = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < str.length; ++i) {
|
||||||
|
var c = str.charCodeAt(i);
|
||||||
|
|
||||||
|
// ASCII
|
||||||
|
if (c < 0x80) {
|
||||||
|
if (noEscape[c] === 1)
|
||||||
|
continue;
|
||||||
|
if (lastPos < i)
|
||||||
|
out += str.slice(lastPos, i);
|
||||||
|
lastPos = i + 1;
|
||||||
|
out += hexTable[c];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPos < i)
|
||||||
|
out += str.slice(lastPos, i);
|
||||||
|
|
||||||
|
// Multi-byte characters ...
|
||||||
|
if (c < 0x800) {
|
||||||
|
lastPos = i + 1;
|
||||||
|
out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c < 0xD800 || c >= 0xE000) {
|
||||||
|
lastPos = i + 1;
|
||||||
|
out += hexTable[0xE0 | (c >> 12)] +
|
||||||
|
hexTable[0x80 | ((c >> 6) & 0x3F)] +
|
||||||
|
hexTable[0x80 | (c & 0x3F)];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Surrogate pair
|
||||||
|
++i;
|
||||||
|
|
||||||
|
if (i >= str.length)
|
||||||
|
throw new errors.URIError('ERR_INVALID_URI');
|
||||||
|
|
||||||
|
var c2 = str.charCodeAt(i) & 0x3FF;
|
||||||
|
|
||||||
|
lastPos = i + 1;
|
||||||
|
c = 0x10000 + (((c & 0x3FF) << 10) | c2);
|
||||||
|
out += hexTable[0xF0 | (c >> 18)] +
|
||||||
|
hexTable[0x80 | ((c >> 12) & 0x3F)] +
|
||||||
|
hexTable[0x80 | ((c >> 6) & 0x3F)] +
|
||||||
|
hexTable[0x80 | (c & 0x3F)];
|
||||||
|
}
|
||||||
|
if (lastPos === 0)
|
||||||
|
return str;
|
||||||
|
if (lastPos < str.length)
|
||||||
|
return out + str.slice(lastPos);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function HttpRequest(method, url, headers, body) {
|
||||||
|
if (method === undefined) {
|
||||||
|
this.method = "";
|
||||||
|
} else {
|
||||||
|
this.method = method;
|
||||||
|
}
|
||||||
|
if (url === undefined) {
|
||||||
|
this.host = "";
|
||||||
|
this.uri = "";
|
||||||
|
this.query = {};
|
||||||
|
} else {
|
||||||
|
this.query = {};
|
||||||
|
var host, path;
|
||||||
|
var i = url.indexOf("://");
|
||||||
|
if (i !== -1) {
|
||||||
|
url = url.substr(i + 3)
|
||||||
|
}
|
||||||
|
var i = url.indexOf("?");
|
||||||
|
if (i !== -1) {
|
||||||
|
var query_str = url.substr(i + 1);
|
||||||
|
url = url.substr(0, i);
|
||||||
|
var spl = query_str.split("&");
|
||||||
|
for (var i in spl) {
|
||||||
|
var kv = spl[i];
|
||||||
|
var index = kv.indexOf('=');
|
||||||
|
var key, value;
|
||||||
|
if (index >= 0) {
|
||||||
|
key = kv.substr(0, index);
|
||||||
|
value = kv.substr(index + 1);
|
||||||
|
} else {
|
||||||
|
key = kv;
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
if (key !== "") {
|
||||||
|
key = decodeURI(key);
|
||||||
|
value = decodeURI(value);
|
||||||
|
if (this.query[key] === undefined) {
|
||||||
|
this.query[key] = [value]
|
||||||
|
} else {
|
||||||
|
this.query[key].push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var i = url.indexOf("/");
|
||||||
|
if (i === -1) {
|
||||||
|
host = url;
|
||||||
|
path = "/";
|
||||||
|
} else {
|
||||||
|
host = url.substr(0, i);
|
||||||
|
path = url.substr(i);
|
||||||
|
}
|
||||||
|
this.host = host;
|
||||||
|
this.uri = decodeURI(path);
|
||||||
|
}
|
||||||
|
if (headers === undefined) {
|
||||||
|
this.headers = {};
|
||||||
|
} else {
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
if (body === undefined) {
|
||||||
|
this.body = "";
|
||||||
|
} else {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findHeader(r, header) {
|
||||||
|
for (var k in r.headers) {
|
||||||
|
if (k.toLowerCase() === header.toLowerCase()) {
|
||||||
|
return r.headers[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a CanonicalRequest from a regular request string
|
||||||
|
//
|
||||||
|
// CanonicalRequest =
|
||||||
|
// HTTPRequestMethod + '\n' +
|
||||||
|
// CanonicalURI + '\n' +
|
||||||
|
// CanonicalQueryString + '\n' +
|
||||||
|
// CanonicalHeaders + '\n' +
|
||||||
|
// SignedHeaders + '\n' +
|
||||||
|
// HexEncode(Hash(RequestPayload))
|
||||||
|
function CanonicalRequest(r, signedHeaders) {
|
||||||
|
var hexencode = findHeader(r, HeaderContentSha256);
|
||||||
|
if (hexencode === null) {
|
||||||
|
var data = RequestPayload(r);
|
||||||
|
hexencode = crypto_wrapper.HexEncodeSHA256Hash(data);
|
||||||
|
}
|
||||||
|
return r.method + "\n" + CanonicalURI(r) + "\n" + CanonicalQueryString(r) + "\n" + CanonicalHeaders(r, signedHeaders) + "\n" + signedHeaders.join(';') + "\n" + hexencode
|
||||||
|
}
|
||||||
|
|
||||||
|
function CanonicalURI(r) {
|
||||||
|
var pattens = r.uri.split('/');
|
||||||
|
var uri = [];
|
||||||
|
for (var k in pattens) {
|
||||||
|
var v = pattens[k];
|
||||||
|
uri.push(urlEncode(v))
|
||||||
|
}
|
||||||
|
var urlpath = uri.join('/');
|
||||||
|
if (urlpath[urlpath.length - 1] !== '/') {
|
||||||
|
urlpath = urlpath + '/'
|
||||||
|
}
|
||||||
|
//r.uri = urlpath
|
||||||
|
return urlpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CanonicalQueryString(r) {
|
||||||
|
var keys = [];
|
||||||
|
for (var key in r.query) {
|
||||||
|
keys.push(key)
|
||||||
|
}
|
||||||
|
keys.sort();
|
||||||
|
var a = [];
|
||||||
|
for (var i in keys) {
|
||||||
|
var key = urlEncode(keys[i]);
|
||||||
|
var value = r.query[keys[i]];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.sort();
|
||||||
|
for (var iv in value) {
|
||||||
|
a.push(key + '=' + urlEncode(value[iv]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
a.push(key + '=' + urlEncode(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
function CanonicalHeaders(r, signedHeaders) {
|
||||||
|
var headers = {};
|
||||||
|
for (var key in r.headers) {
|
||||||
|
headers[key.toLowerCase()] = r.headers[key];
|
||||||
|
}
|
||||||
|
var a = [];
|
||||||
|
for (var i in signedHeaders) {
|
||||||
|
var value = headers[signedHeaders[i]];
|
||||||
|
a.push(signedHeaders[i] + ':' + value.trim())
|
||||||
|
}
|
||||||
|
return a.join('\n') + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
function SignedHeaders(r) {
|
||||||
|
var a = [];
|
||||||
|
for (var key in r.headers) {
|
||||||
|
a.push(key.toLowerCase())
|
||||||
|
}
|
||||||
|
a.sort();
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
function RequestPayload(r) {
|
||||||
|
return r.body
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a "String to Sign".
|
||||||
|
function StringToSign(canonicalRequest, t) {
|
||||||
|
var bytes = crypto_wrapper.HexEncodeSHA256Hash(canonicalRequest);
|
||||||
|
return Algorithm + "\n" + t + "\n" + bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the HWS Signature.
|
||||||
|
function SignStringToSign(stringToSign, signingKey) {
|
||||||
|
return crypto_wrapper.hmacsha256(signingKey, stringToSign)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the finalized value for the "Authorization" header. The signature
|
||||||
|
// parameter is the output from SignStringToSign
|
||||||
|
function AuthHeaderValue(signature, Key, signedHeaders) {
|
||||||
|
return Algorithm + " Access=" + Key + ", SignedHeaders=" + signedHeaders.join(';') + ", Signature=" + signature
|
||||||
|
}
|
||||||
|
|
||||||
|
function twoChar(s) {
|
||||||
|
if (s >= 10) {
|
||||||
|
return "" + s
|
||||||
|
} else {
|
||||||
|
return "0" + s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTime() {
|
||||||
|
var date = new Date();
|
||||||
|
return "" + date.getUTCFullYear() + twoChar(date.getUTCMonth() + 1) + twoChar(date.getUTCDate()) + "T" +
|
||||||
|
twoChar(date.getUTCHours()) + twoChar(date.getUTCMinutes()) + twoChar(date.getUTCSeconds()) + "Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Signer() {
|
||||||
|
this.Key = "";
|
||||||
|
this.Secret = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Signer.prototype.Sign = function (r) {
|
||||||
|
var headerTime = findHeader(r, HeaderXDate);
|
||||||
|
if (headerTime === null) {
|
||||||
|
headerTime = getTime();
|
||||||
|
r.headers[HeaderXDate] = headerTime
|
||||||
|
}
|
||||||
|
if (r.method !== "PUT" && r.method !== "PATCH" && r.method !== "POST") {
|
||||||
|
r.body = ""
|
||||||
|
}
|
||||||
|
var queryString = CanonicalQueryString(r);
|
||||||
|
if (queryString !== "") {
|
||||||
|
queryString = "?" + queryString
|
||||||
|
}
|
||||||
|
var options = {
|
||||||
|
hostname: r.host,
|
||||||
|
path: encodeURI(r.uri) + queryString,
|
||||||
|
method: r.method,
|
||||||
|
headers: r.headers
|
||||||
|
};
|
||||||
|
if (findHeader(r, 'host') === null) {
|
||||||
|
r.headers.host = r.host;
|
||||||
|
}
|
||||||
|
var signedHeaders = SignedHeaders(r);
|
||||||
|
var canonicalRequest = CanonicalRequest(r, signedHeaders);
|
||||||
|
var stringToSign = StringToSign(canonicalRequest, headerTime);
|
||||||
|
var signature = SignStringToSign(stringToSign, this.Secret);
|
||||||
|
options.headers[HeaderAuthorization] = AuthHeaderValue(signature, this.Key, signedHeaders);
|
||||||
|
return options
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
HttpRequest: HttpRequest,
|
||||||
|
Signer: Signer,
|
||||||
|
urlEncode: urlEncode,
|
||||||
|
findHeader: findHeader,
|
||||||
|
SignedHeaders: SignedHeaders,
|
||||||
|
CanonicalRequest: CanonicalRequest,
|
||||||
|
StringToSign: StringToSign,
|
||||||
|
}
|
||||||
|
}));
|
||||||
10
server/APIGW-javascript-sdk-2.0.6/test.js
Normal file
10
server/APIGW-javascript-sdk-2.0.6/test.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
var assert = require("assert");
|
||||||
|
var signer = require('./signer')
|
||||||
|
|
||||||
|
var s = ""
|
||||||
|
for (i = 0; i < 0x80; i++) {
|
||||||
|
s = s + signer.urlEncode(String.fromCharCode(i))
|
||||||
|
}
|
||||||
|
console.log(s)
|
||||||
|
assert.equal(s, "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F")
|
||||||
|
|
||||||
203
server/index.js
203
server/index.js
@@ -2,7 +2,8 @@ require('dotenv').config();
|
|||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const crypto = require('crypto');
|
const https = require('https');
|
||||||
|
const signer = require('./APIGW-javascript-sdk-2.0.6/signer');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 3001;
|
const PORT = 3001;
|
||||||
@@ -11,172 +12,66 @@ app.use(cors());
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
|
|
||||||
const METASTUDIO_API_BASE = 'https://metastudio.cn-north-4.myhuaweicloud.com';
|
const METASTUDIO_API_BASE = 'https://metastudio.cn-north-4.myhuaweicloud.com';
|
||||||
const PROJECT_ID = process.env.HUAWEI_PROJECT_ID || 'c67bf5e7d1f84d20a6eefff2c1ee9cd4';
|
|
||||||
const IAM_TOKEN_URL = 'https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens';
|
|
||||||
|
|
||||||
async function getIamToken() {
|
|
||||||
const username = process.env.HUAWEI_USERNAME;
|
|
||||||
const password = process.env.HUAWEI_PASSWORD;
|
|
||||||
const domainName = process.env.HUAWEI_DOMAIN_NAME || domainName;
|
|
||||||
const region = 'cn-north-4';
|
|
||||||
|
|
||||||
if (!username || !password || !domainName) {
|
|
||||||
throw new Error('请配置环境变量: HUAWEI_USERNAME, HUAWEI_PASSWORD, HUAWEI_DOMAIN_NAME');
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestBody = {
|
|
||||||
"auth": {
|
|
||||||
"identity": {
|
|
||||||
"methods": ["password"],
|
|
||||||
"password": {
|
|
||||||
"user": {
|
|
||||||
"name": username,
|
|
||||||
"password": password,
|
|
||||||
"domain": {
|
|
||||||
"name": domainName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scope": {
|
|
||||||
"project": {
|
|
||||||
"name": region
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(IAM_TOKEN_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(requestBody)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`IAM认证失败: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = response.headers.get('X-Subject-Token');
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildAKSKSignature(method, host, uri, query, date, headers) {
|
|
||||||
const secretKey = process.env.HUAWEI_SECRET_KEY;
|
|
||||||
if (!secretKey) {
|
|
||||||
throw new Error('请配置环境变量: HUAWEI_SECRET_KEY');
|
|
||||||
}
|
|
||||||
|
|
||||||
const signingKey = 'sdk-hmac-sha256';
|
|
||||||
const dateShort = date.toISOString().slice(0, 10).replace(/-/g, '');
|
|
||||||
|
|
||||||
const canonicalRequestParts = [
|
|
||||||
method.toUpperCase(),
|
|
||||||
uri,
|
|
||||||
query || '',
|
|
||||||
`${headers.toLowerCase()}\n${host.toLowerCase()}`
|
|
||||||
];
|
|
||||||
const canonicalRequest = canonicalRequestParts.join('\n');
|
|
||||||
|
|
||||||
const stringToSign = [
|
|
||||||
signingKey,
|
|
||||||
date,
|
|
||||||
`${dateShort}/${region}/metastudio/request`,
|
|
||||||
crypto.createHash('sha256').update(canonicalRequest).digest('hex')
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
const signature = crypto
|
|
||||||
.createHmac('sha256', secretKey)
|
|
||||||
.update(stringToSign)
|
|
||||||
.digest('base64');
|
|
||||||
|
|
||||||
return {
|
|
||||||
signature,
|
|
||||||
stringToSign,
|
|
||||||
dateShort
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createOnceCodeWithAKSK() {
|
async function createOnceCodeWithAKSK() {
|
||||||
const accessKey = process.env.HUAWEI_AK;
|
const accessKey = process.env.HUAWEI_AK;
|
||||||
const secretKey = process.env.HUAWEI_SK;
|
const secretKey = process.env.HUAWEI_SK;
|
||||||
const region = 'cn-north-4';
|
const projectId = process.env.HUAWEI_PROJECT_ID;
|
||||||
|
|
||||||
if (!accessKey || !secretKey) {
|
if (!accessKey || !secretKey) {
|
||||||
throw new Error('请配置环境变量: HUAWEI_AK 和 HUAWEI_SK');
|
throw new Error('请配置环境变量: HUAWEI_AK 和 HUAWEI_SK');
|
||||||
}
|
}
|
||||||
|
if (!projectId) {
|
||||||
const host = 'metastudio.cn-north-4.myhuaweicloud.com';
|
throw new Error('请配置环境变量: HUAWEI_PROJECT_ID');
|
||||||
const uri = `/v1/${PROJECT_ID}/digital-human-chat/once-code`;
|
|
||||||
const method = 'POST';
|
|
||||||
const date = new Date().toUTCString();
|
|
||||||
const query = '';
|
|
||||||
|
|
||||||
const { signature, stringToSign, dateShort } = buildAKSKSignature(
|
|
||||||
method, host, uri, query, date, ''
|
|
||||||
);
|
|
||||||
|
|
||||||
const authHeader = `SDK-HMAC-SHA256 Access=${accessKey}, SignedHeaders=host;x-sdk-date, Signature=${signature}`;
|
|
||||||
|
|
||||||
const response = await fetch(`${METASTUDIO_API_BASE}${uri}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Sdk-Date': date,
|
|
||||||
'Authorization': authHeader,
|
|
||||||
'X-Project-Id': PROJECT_ID
|
|
||||||
},
|
|
||||||
body: JSON.stringify({})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
throw new Error(`API调用失败: ${response.status} - ${errorText}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const uri = `/v1/${projectId}/digital-human-chat/once-code`;
|
||||||
return data.once_code;
|
|
||||||
|
const sig = new signer.Signer();
|
||||||
|
sig.Key = accessKey;
|
||||||
|
sig.Secret = secretKey;
|
||||||
|
|
||||||
|
const req = new signer.HttpRequest(
|
||||||
|
'POST',
|
||||||
|
`${METASTUDIO_API_BASE}${uri}`,
|
||||||
|
{
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'x-project-id': projectId
|
||||||
|
},
|
||||||
|
'{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = sig.Sign(req);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const request = https.request(options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
res.on('end', () => {
|
||||||
|
if (res.statusCode !== 200) {
|
||||||
|
reject(new Error(`API调用失败: ${res.statusCode} - ${data}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = JSON.parse(data);
|
||||||
|
resolve(response.once_code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', (e) => {
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
request.write('{}');
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.post('/api/once-code', async (req, res) => {
|
app.post('/api/once-code', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
let onceCode;
|
|
||||||
|
|
||||||
if (process.env.HUAWEI_AK && process.env.HUAWEI_SK) {
|
|
||||||
console.log('使用 AK/SK 认证获取授权码...');
|
console.log('使用 AK/SK 认证获取授权码...');
|
||||||
onceCode = await createOnceCodeWithAKSK();
|
const onceCode = await createOnceCodeWithAKSK();
|
||||||
} else if (process.env.HUAWEI_USERNAME && process.env.HUAWEI_PASSWORD) {
|
|
||||||
console.log('使用 Token 认证获取授权码...');
|
|
||||||
const token = await getIamToken();
|
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
`${METASTUDIO_API_BASE}/v1/${PROJECT_ID}/digital-human-chat/once-code`,
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Auth-Token': token,
|
|
||||||
'X-Project-Id': PROJECT_ID
|
|
||||||
},
|
|
||||||
body: JSON.stringify({})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
throw new Error(`API调用失败: ${response.status} - ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
onceCode = data.once_code;
|
|
||||||
} else {
|
|
||||||
return res.status(400).json({
|
|
||||||
error_code: 'CONFIG_MISSING',
|
|
||||||
error_msg: '请配置环境变量 (AK/SK 或用户名密码)'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({ once_code: onceCode });
|
res.json({ once_code: onceCode });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取授权码失败:', error);
|
console.error('获取授权码失败:', error);
|
||||||
@@ -193,7 +88,5 @@ app.get('/health', (req, res) => {
|
|||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`后端服务已启动: http://localhost:${PORT}`);
|
console.log(`后端服务已启动: http://localhost:${PORT}`);
|
||||||
console.log('支持的认证方式:');
|
console.log('使用 AK/SK 认证方式');
|
||||||
console.log(' 1. AK/SK 认证: 设置 HUAWEI_AK 和 HUAWEI_SK');
|
|
||||||
console.log(' 2. Token 认证: 设置 HUAWEI_USERNAME, HUAWEI_PASSWORD, HUAWEI_DOMAIN_NAME');
|
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
server/sdk.zip
Normal file
BIN
server/sdk.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user