Skip to content

Commit

Permalink
fix(auth): new IAM auth grpc service per token update
Browse files Browse the repository at this point in the history
Update credentials.ts and utils.ts to create
instance of GrpcSerivce per token update instead
of using one instance per application. This must
help with unhandled disconnections ydb-platform#175 ydb-platform#150
  • Loading branch information
zeruk committed Nov 28, 2022
1 parent 08b0fc9 commit 341872a
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ build/

#personal
.idea/
.vscode/
.DS_Store

# git merge leftovers
Expand Down
51 changes: 40 additions & 11 deletions src/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,38 @@ export class TokenAuthService implements IAuthService {
}
}

export class IamAuthService extends GrpcService<IamTokenService> implements IAuthService {
class TempIamAuthService extends GrpcService<IamTokenService> {
constructor(iamCredentials: IIamCredentials, sslCredentials: ISslCredentials) {
super(
iamCredentials.iamEndpoint,
'yandex.cloud.iam.v1.IamTokenService',
IamTokenService,
sslCredentials,
);
}

create(request: yandex.cloud.iam.v1.ICreateIamTokenRequest) {
return this.api.create(request)
}

destroy() {
this.api.end()
}
}

export class IamAuthService implements IAuthService {
private jwtExpirationTimeout = 3600 * 1000;
private tokenExpirationTimeout = 120 * 1000;
private tokenRequestTimeout = 10 * 1000;
private token: string = '';
private tokenTimestamp: DateTime|null;
private tokenUpdateInProgress: Boolean = false;
private readonly iamCredentials: IIamCredentials;
private readonly sslCredentials: ISslCredentials

constructor(iamCredentials: IIamCredentials, sslCredentials?: ISslCredentials) {
super(
iamCredentials.iamEndpoint,
'yandex.cloud.iam.v1.IamTokenService',
IamTokenService,
sslCredentials || makeDefaultSslCredentials(),
);
this.iamCredentials = iamCredentials;
this.sslCredentials = sslCredentials || makeDefaultSslCredentials()
this.tokenTimestamp = null;
}

Expand All @@ -88,24 +104,37 @@ export class IamAuthService extends GrpcService<IamTokenService> implements IAut
);
}

private sendTokenRequest(): Promise<ICreateIamTokenResponse> {
const tokenPromise = this.api.create({jwt: this.getJwtRequest()});
return withTimeout<ICreateIamTokenResponse>(tokenPromise, this.tokenRequestTimeout);
private async sendTokenRequest(): Promise<ICreateIamTokenResponse> {
let tempIamAuthService = new TempIamAuthService(this.iamCredentials, this.sslCredentials)
const tokenPromise = tempIamAuthService.create({jwt: this.getJwtRequest()});
const result = await withTimeout<ICreateIamTokenResponse>(tokenPromise, this.tokenRequestTimeout);
tempIamAuthService.destroy()
return result
}

private async updateToken() {
this.tokenUpdateInProgress=true
const {iamToken} = await this.sendTokenRequest();
if (iamToken) {
this.token = iamToken;
this.tokenTimestamp = DateTime.utc();
this.tokenUpdateInProgress=false
} else {
this.tokenUpdateInProgress=false
throw new Error('Received empty token from IAM!');
}
}

private async waitUntilTokenUpdated() {
while(this.tokenUpdateInProgress) await sleep(1)
return
}

public async getAuthMetadata(): Promise<grpc.Metadata> {
if (this.expired) {
await this.updateToken();
// block updateToken calls while token updating
if(this.tokenUpdateInProgress) await this.waitUntilTokenUpdated()
else await this.updateToken();
}
return makeCredentialsMetadata(this.token);
}
Expand Down
5 changes: 5 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export abstract class GrpcService<Api extends $protobuf.rpc.Service> {
new grpc.Client(host, grpc.credentials.createSsl(sslCredentials.rootCertificates, sslCredentials.clientPrivateKey, sslCredentials.clientCertChain)) :
new grpc.Client(host, grpc.credentials.createInsecure());
const rpcImpl: $protobuf.RPCImpl = (method, requestData, callback) => {
if(null===method && requestData === null && callback === null) {
// signal `end` from protobuf service
client.close()
return
}
const path = `/${this.name}/${method.name}`;
client.makeUnaryRequest(path, _.identity, _.identity, requestData, callback);
};
Expand Down
8 changes: 8 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig-base.json",
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"outDir": "build/cjs"
}
}

0 comments on commit 341872a

Please sign in to comment.