feat: 添加华为云API网关SDK并重构认证逻辑
重构服务器端认证逻辑,使用华为云API网关SDK替代原有实现 新增自动获取授权码功能,当用户未输入时自动获取 添加SDK相关测试文件和许可证
This commit is contained in:
@@ -181,13 +181,25 @@ async function initializeSDK() {
|
||||
try {
|
||||
const serverAddress = elements.serverAddress.value.trim();
|
||||
const robotId = elements.robotId.value.trim();
|
||||
const onceCode = elements.onceCode.value.trim();
|
||||
let onceCode = elements.onceCode.value.trim();
|
||||
|
||||
if (!serverAddress) {
|
||||
logger.error('请输入服务器地址');
|
||||
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...');
|
||||
|
||||
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")
|
||||
|
||||
205
server/index.js
205
server/index.js
@@ -2,7 +2,8 @@ require('dotenv').config();
|
||||
|
||||
const express = require('express');
|
||||
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 PORT = 3001;
|
||||
@@ -11,172 +12,66 @@ app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
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() {
|
||||
const accessKey = process.env.HUAWEI_AK;
|
||||
const secretKey = process.env.HUAWEI_SK;
|
||||
const region = 'cn-north-4';
|
||||
const projectId = process.env.HUAWEI_PROJECT_ID;
|
||||
|
||||
if (!accessKey || !secretKey) {
|
||||
throw new Error('请配置环境变量: HUAWEI_AK 和 HUAWEI_SK');
|
||||
}
|
||||
|
||||
const host = 'metastudio.cn-north-4.myhuaweicloud.com';
|
||||
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}`);
|
||||
if (!projectId) {
|
||||
throw new Error('请配置环境变量: HUAWEI_PROJECT_ID');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.once_code;
|
||||
const uri = `/v1/${projectId}/digital-human-chat/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) => {
|
||||
try {
|
||||
let onceCode;
|
||||
|
||||
if (process.env.HUAWEI_AK && process.env.HUAWEI_SK) {
|
||||
console.log('使用 AK/SK 认证获取授权码...');
|
||||
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 或用户名密码)'
|
||||
});
|
||||
}
|
||||
|
||||
console.log('使用 AK/SK 认证获取授权码...');
|
||||
const onceCode = await createOnceCodeWithAKSK();
|
||||
res.json({ once_code: onceCode });
|
||||
} catch (error) {
|
||||
console.error('获取授权码失败:', error);
|
||||
@@ -193,7 +88,5 @@ app.get('/health', (req, res) => {
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`后端服务已启动: http://localhost:${PORT}`);
|
||||
console.log('支持的认证方式:');
|
||||
console.log(' 1. AK/SK 认证: 设置 HUAWEI_AK 和 HUAWEI_SK');
|
||||
console.log(' 2. Token 认证: 设置 HUAWEI_USERNAME, HUAWEI_PASSWORD, HUAWEI_DOMAIN_NAME');
|
||||
console.log('使用 AK/SK 认证方式');
|
||||
});
|
||||
|
||||
BIN
server/sdk.zip
Normal file
BIN
server/sdk.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user