Skip to content

Commit

Permalink
feat: 支持STS认证
Browse files Browse the repository at this point in the history
  • Loading branch information
otakustay committed Sep 6, 2022
1 parent f40e20a commit 7495311
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 72 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"type": "module",
"exports": {
".": {
"import": "./es/index.js",
"require": "./cjs/index.js"
"require": "./cjs/index.js",
"import": "./es/index.js"
}
},
"module": "./es/index.js",
Expand Down
6 changes: 1 addition & 5 deletions src/authorization.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import crypto from 'node:crypto';
import {BceCredential} from './interface.js';

// https://cloud.baidu.com/doc/Reference/s/njwvz1yfu

export interface BceCredential {
ak: string;
sk: string;
}

export interface RequestInfo {
params: Array<[string, string]> | null;
headers: Record<string, string>;
Expand Down
11 changes: 4 additions & 7 deletions src/bls.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {BceCredential} from './authorization.js';
import {Http} from './http.js';
import {RegionClientOptions} from './interface.js';

// https://cloud.baidu.com/doc/BLS/s/Ck8qtng1c

Expand All @@ -23,16 +23,13 @@ export interface QueryLogRecordResponse {
resultSet?: LogRecordResultSet;
}

export interface BlsOptions {
region: string;
credentials: BceCredential;
}
export type BlsOptions = RegionClientOptions;

export class BlsClient {
private readonly http: Http;

constructor({region, credentials}: BlsOptions) {
this.http = Http.fromRegionSupportedServiceId('bls-log', region, credentials);
constructor(options: BlsOptions) {
this.http = Http.fromRegionSupportedServiceId('bls-log', options);
}

async queryLogRecord({logStoreName, query, startDateTime, endDateTime}: LogRecordQuery) {
Expand Down
13 changes: 5 additions & 8 deletions src/bos.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs, {ReadStream} from 'node:fs';
import {BceCredential} from './authorization.js';
import {Http} from './http.js';
import {RegionClientOptions} from './interface.js';

// https://cloud.baidu.com/doc/BOS/s/Rjwvysdnp

Expand Down Expand Up @@ -47,18 +47,15 @@ export interface PutObjectOptions {

export type ObjectBody = string | Blob | ArrayBuffer | ReadStream;

export interface BosOptions {
region: string;
credentials: BceCredential;
}
export type BosOptions = RegionClientOptions;

export class BosClient {
private readonly hostBase: string;
private readonly http: Http;

constructor({region, credentials}: BosOptions) {
this.hostBase = `${region}.bcebos.com`;
this.http = Http.fromEndpoint(this.hostBase, credentials);
constructor(options: BosOptions) {
this.hostBase = `${options.region}.bcebos.com`;
this.http = Http.fromEndpoint(this.hostBase, options);
}

async listObjects(bucketName: string, options?: ListObjectOptions) {
Expand Down
74 changes: 37 additions & 37 deletions src/http.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Blob} from 'node:buffer';
import {fromPairs} from 'ramda';
import {Authorization, BceCredential} from './authorization.js';
import {BceCredentialContext, RegionClientOptions} from './interface.js';
import {Authorization} from './authorization.js';
import {RequestError} from './error.js';

const stringifyDate = (date: Date) => date.toISOString().replace(/\.\d+Z$/, 'Z');
Expand Down Expand Up @@ -65,24 +66,26 @@ export class Http {
private readonly baseUrl: string;
private readonly authorization: Authorization;
private readonly host: string;
private readonly sessionToken: string | undefined;

private constructor(endpoint: string, credentials: BceCredential) {
private constructor(endpoint: string, context: BceCredentialContext) {
const url = new URL(`https://${endpoint}`);
this.host = url.host;
this.baseUrl = url.origin;
this.authorization = new Authorization(credentials);
this.sessionToken = context.sessionToken;
this.authorization = new Authorization(context.credentials);
}

static fromEndpoint(endpoint: string, credentials: BceCredential) {
return new Http(endpoint, credentials);
static fromEndpoint(endpoint: string, context: BceCredentialContext) {
return new Http(endpoint, context);
}

static fromRegionSupportedServiceId(serviceId: string, region: string, credentials: BceCredential) {
return new Http(`${serviceId}.${region}.${BASE_DOMAIN}`, credentials);
static fromRegionSupportedServiceId(serviceId: string, context: RegionClientOptions) {
return new Http(`${serviceId}.${context.region}.${BASE_DOMAIN}`, context);
}

static fromServiceId(serviceId: string, credentials: BceCredential) {
return new Http(`${serviceId}.${BASE_DOMAIN}`, credentials);
static fromServiceId(serviceId: string, context: RegionClientOptions) {
return new Http(`${serviceId}.${BASE_DOMAIN}`, context);
}

async json<T>(method: string, url: string, options: RequestOptions): Promise<ClientResponse<T>> {
Expand Down Expand Up @@ -116,45 +119,27 @@ export class Http {
}

private async request(method: string, url: string, options: RequestOptions) {
const {headers, params} = options;
const searchParams = constructSearchParams(options.params);
const timestamp = stringifyDate(new Date());
const searchParams = constructSearchParams(params);
const authorization = this.authorization.authorize(
const headers = this.constructHeaders(options);
headers['x-bce-date'] = timestamp;
headers.authorization = this.authorization.authorize(
{
method,
url,
headers,
params: searchParams && [...searchParams.entries()],
headers: {
host: this.host,
'x-bce-date': timestamp,
...options.headers,
},
},
{timestamp}
);
const request: RequestInit = {
method,
headers: {
...headers,
authorization,
'x-bce-date': timestamp,
},
};

if (isPlainObject(options.body)) {
request.body = JSON.stringify(options.body);
Object.assign(
request.headers!,
{'content-type': 'application/json'}
);
}
else if (options.body) {
request.body = options.body;
}

const response = await fetch(
this.baseUrl + url + (searchParams ? `?${searchParams}` : ''),
request
{
method,
headers,
body: isPlainObject(options.body) ? JSON.stringify(options.body) : (options.body ?? null),
}
);

const responseHeaders = fromPairs([...response.headers.entries()]);
Expand All @@ -166,4 +151,19 @@ export class Http {
const body = await response.text();
throw new RequestError(response.status, responseHeaders, body);
}

private constructHeaders(options: RequestOptions) {
const headers: Record<string, string> = {
// 允许覆盖`host`头,但`x-bce-date`永远是内部生成的,不然签名过不去
host: this.host,
...options.headers,
};
if (this.sessionToken) {
headers['x-bce-security-token'] = this.sessionToken;
}
if (isPlainObject(options.body)) {
headers['content-type'] ??= 'application/json';
}
return headers;
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export {BceCredential} from './authorization.js';
export * from './interface.js';
export * from './error.js';
export * from './bls.js';
export * from './bos.js';
Expand Down
13 changes: 13 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface BceCredential {
ak: string;
sk: string;
}

export interface BceCredentialContext {
credentials: BceCredential;
sessionToken?: string;
}

export interface RegionClientOptions extends BceCredentialContext {
region: string;
}
33 changes: 21 additions & 12 deletions src/sts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {BceCredential} from './authorization.js';
import {RegionClientOptions} from './interface.js';
import {Http} from './http.js';

// https://cloud.baidu.com/doc/IAM/s/Qjwvyc8ov
Expand All @@ -11,9 +11,21 @@ interface AssumeRoleOptions {
acl?: string;
}

// https://cloud.baidu.com/doc/BOS/s/Tjwvysda9

interface AccessControlDescription {
id?: string;
eid?: string;
service: string;
resource: string[];
region: string;
effect: 'Allow' | 'Deny';
permission: string[];
}

interface SessionTokenOptions {
durationSeconds?: number;
acl?: string;
accessControlList: AccessControlDescription[];
attachment?: string;
}

Expand All @@ -26,27 +38,24 @@ interface SessionTokenResponse {
userId: string;
}

export interface StsOptions {
region: string;
credentials: BceCredential;
}
export type StsOptions = RegionClientOptions;

export class StsClient {
private readonly http: Http;

constructor({region, credentials}: StsOptions) {
this.http = Http.fromRegionSupportedServiceId('sts', region, credentials);
constructor(options: StsOptions) {
this.http = Http.fromRegionSupportedServiceId('sts', options);
}

async getSessionToken(options?: SessionTokenOptions) {
async getSessionToken(options: SessionTokenOptions) {
const response = await this.http.json<SessionTokenResponse>(
'POST',
'/v1/sessionToken',
{
params: {durationSeconds: options?.durationSeconds},
params: {durationSeconds: options.durationSeconds},
body: {
acl: options?.acl,
attachment: options?.attachment,
accessControlList: options.accessControlList,
attachment: options.attachment,
},
}
);
Expand Down

0 comments on commit 7495311

Please sign in to comment.