|
1 | 1 | import * as grpc from '@grpc/grpc-js';
|
2 | 2 | import jwt from 'jsonwebtoken';
|
3 | 3 | import {DateTime} from 'luxon';
|
4 |
| -import {GrpcService, sleep, withTimeout} from "./utils"; |
| 4 | +import {getOperationPayload, GrpcService, sleep, withTimeout} from "./utils"; |
5 | 5 | import {MetadataTokenService} from '@yandex-cloud/nodejs-sdk/dist/token-service/metadata-token-service';
|
6 | 6 | import {TokenService} from "@yandex-cloud/nodejs-sdk/dist/types";
|
7 |
| -import {yandex} from "ydb-sdk-proto"; |
| 7 | +import {yandex, Ydb} from "ydb-sdk-proto"; |
8 | 8 | import {ISslCredentials, makeDefaultSslCredentials} from './ssl-credentials';
|
9 | 9 | import IamTokenService = yandex.cloud.iam.v1.IamTokenService;
|
| 10 | +import AuthServiceResult = Ydb.Auth.LoginResult; |
10 | 11 | import ICreateIamTokenResponse = yandex.cloud.iam.v1.ICreateIamTokenResponse;
|
11 | 12 |
|
12 | 13 | function makeCredentialsMetadata(token: string): grpc.Metadata {
|
@@ -39,6 +40,90 @@ export class AnonymousAuthService implements IAuthService {
|
39 | 40 | }
|
40 | 41 | }
|
41 | 42 |
|
| 43 | + |
| 44 | +export class StaticCredentialsAuthService implements IAuthService { |
| 45 | + private tokenExpirationTimeout = 30 * 60 * 1000; |
| 46 | + private tokenRequestTimeout = 10 * 1000; |
| 47 | + private tokenTimestamp: DateTime|null; |
| 48 | + private token: string = ''; |
| 49 | + private tokenUpdateInProgress: Boolean = false; |
| 50 | + private user: string; |
| 51 | + private password: string; |
| 52 | + private endpoint: string |
| 53 | + private sslCredentials: ISslCredentials | undefined |
| 54 | + |
| 55 | + private readonly GrpcService = class extends GrpcService<Ydb.Auth.V1.AuthService> { |
| 56 | + constructor(endpoint: string, sslCredentials?: ISslCredentials) { |
| 57 | + super(endpoint, 'Ydb.Auth.V1.AuthService', Ydb.Auth.V1.AuthService, sslCredentials) |
| 58 | + } |
| 59 | + |
| 60 | + login(request: Ydb.Auth.ILoginRequest) { |
| 61 | + return this.api.login(request) |
| 62 | + } |
| 63 | + |
| 64 | + destroy() { this.api.end() } |
| 65 | + |
| 66 | + } |
| 67 | + |
| 68 | + constructor(user: string, password: string, endpoint: string, sslCredentials?: ISslCredentials) { |
| 69 | + this.user = user; |
| 70 | + this.password = password; |
| 71 | + this.endpoint = endpoint; |
| 72 | + this.sslCredentials = sslCredentials |
| 73 | + this.tokenTimestamp = null; |
| 74 | + } |
| 75 | + |
| 76 | + private get expired() { |
| 77 | + return !this.tokenTimestamp || ( |
| 78 | + DateTime.utc().diff(this.tokenTimestamp).valueOf() > this.tokenExpirationTimeout |
| 79 | + ); |
| 80 | + } |
| 81 | + |
| 82 | + private async sendTokenRequest(): Promise<AuthServiceResult> { |
| 83 | + let runtimeAuthService = new this.GrpcService(this.endpoint, this.sslCredentials) |
| 84 | + try { |
| 85 | + const tokenPromise = runtimeAuthService.login({ |
| 86 | + user: this.user, |
| 87 | + password: this.password |
| 88 | + }); |
| 89 | + const response = await withTimeout<Ydb.Auth.LoginResponse>(tokenPromise, this.tokenRequestTimeout); |
| 90 | + const result = AuthServiceResult.decode(getOperationPayload(response)); |
| 91 | + runtimeAuthService.destroy() |
| 92 | + return result |
| 93 | + } catch (error) { |
| 94 | + throw new Error("Can't login by user and password " + String(error)) |
| 95 | + } |
| 96 | + |
| 97 | + } |
| 98 | + |
| 99 | + private async updateToken() { |
| 100 | + this.tokenUpdateInProgress = true |
| 101 | + const {token} = await this.sendTokenRequest(); |
| 102 | + if (token) { |
| 103 | + this.token = token; |
| 104 | + this.tokenTimestamp = DateTime.utc(); |
| 105 | + this.tokenUpdateInProgress = false |
| 106 | + } else { |
| 107 | + this.tokenUpdateInProgress = false |
| 108 | + throw new Error('Received empty token from credentials!'); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + private async waitUntilTokenUpdated() { |
| 113 | + while (this.tokenUpdateInProgress) { await sleep(1) } |
| 114 | + return |
| 115 | + } |
| 116 | + |
| 117 | + public async getAuthMetadata(): Promise<grpc.Metadata> { |
| 118 | + if (this.expired) { |
| 119 | + // block updateToken calls while token updating |
| 120 | + if(this.tokenUpdateInProgress) await this.waitUntilTokenUpdated() |
| 121 | + else await this.updateToken(); |
| 122 | + } |
| 123 | + return makeCredentialsMetadata(this.token); |
| 124 | + } |
| 125 | +} |
| 126 | + |
42 | 127 | export class TokenAuthService implements IAuthService {
|
43 | 128 | constructor(private token: string) {}
|
44 | 129 |
|
|
0 commit comments