1- import { Ydb } from "ydb-sdk-proto" ;
1+ import { Ydb } from "ydb-sdk-proto" ;
22import AuthServiceResult = Ydb . Auth . LoginResult ;
3- import { ISslCredentials } from "../utils/ssl-credentials" ;
4- import { GrpcService , withTimeout } from "../utils" ;
5- import { retryable } from "../retries_obsoleted" ;
6- import { DateTime } from "luxon" ;
7- import { getOperationPayload } from "../utils/process-ydb-operation-result" ;
3+ import { ISslCredentials } from "../utils/ssl-credentials" ;
4+ import { GrpcService , withTimeout } from "../utils" ;
5+ import { retryable } from "../retries_obsoleted" ;
6+ import { getOperationPayload } from "../utils/process-ydb-operation-result" ;
87import * as grpc from "@grpc/grpc-js" ;
9- import { addCredentialsToMetadata } from "./add-credentials-to-metadata" ;
8+ import { addCredentialsToMetadata } from "./add-credentials-to-metadata" ;
109
11- import { IAuthService } from "./i-auth-service" ;
12- import { HasLogger } from "../logger/has-logger" ;
13- import { Logger } from "../logger/simple-logger" ;
14- import { getDefaultLogger } from "../logger/get-default-logger" ;
10+ import { IAuthService } from "./i-auth-service" ;
11+ import { HasLogger } from "../logger/has-logger" ;
12+ import { Logger } from "../logger/simple-logger" ;
13+ import { getDefaultLogger } from "../logger/get-default-logger" ;
1514
15+ /**
16+ * Static credentials token.
17+ */
18+ export type StaticCredentialsToken = {
19+ value : string
20+ aud : string [ ]
21+ exp : number
22+ iat : number
23+ sub : string
24+ }
25+
26+ /**
27+ * Interface for options used in static credentials authentication.
28+ */
1629interface StaticCredentialsAuthOptions {
17- /** Custom ssl sertificates . If you use it in driver, you must use it here too */
30+ /** Custom SSL certificates . If you use it in driver, you must use it here too */
1831 sslCredentials ?: ISslCredentials ;
32+
1933 /**
2034 * Timeout for token request in milliseconds
2135 * @default 10 * 1000
2236 */
2337 tokenRequestTimeout ?: number ;
24- /** Expiration time for token in milliseconds
38+
39+ /**
40+ * Expiration time for token in milliseconds
41+ * @deprecated Use tokenRefreshInterval instead
2542 * @default 6 * 60 * 60 * 1000
2643 */
27- tokenExpirationTimeout ?: number
44+ tokenExpirationTimeout ?: number ;
45+
46+ /**
47+ * Time interval in milliseconds after which the token will be refreshed.
48+ * When specified, token refresh is based on this timer rather than the token's exp field.
49+ */
50+ tokenRefreshInterval ?: number ;
2851}
2952
3053class StaticCredentialsGrpcService extends GrpcService < Ydb . Auth . V1 . AuthService > implements HasLogger {
@@ -44,16 +67,19 @@ class StaticCredentialsGrpcService extends GrpcService<Ydb.Auth.V1.AuthService>
4467
4568export class StaticCredentialsAuthService implements IAuthService {
4669 private readonly tokenRequestTimeout = 10 * 1000 ;
47- private readonly tokenExpirationTimeout = 6 * 60 * 60 * 1000 ;
48- private tokenTimestamp : DateTime | null ;
49- private token : string = '' ;
50- private tokenUpdatePromise : Promise < any > | null = null ;
51- private user : string ;
52- private password : string ;
53- private endpoint : string ;
54- private sslCredentials : ISslCredentials | undefined ;
70+ private readonly tokenRefreshInterval : number | null = null ;
71+
72+ private readonly user : string ;
73+ private readonly password : string ;
74+ private readonly endpoint : string ;
75+ private readonly sslCredentials : ISslCredentials | undefined ;
76+
5577 public readonly logger : Logger ;
5678
79+ private token : StaticCredentialsToken | null = null ;
80+ // Mutex
81+ private promise : Promise < grpc . Metadata > | null = null ;
82+
5783 constructor (
5884 user : string ,
5985 password : string ,
@@ -74,25 +100,37 @@ export class StaticCredentialsAuthService implements IAuthService {
74100 loggerOrOptions ?: Logger | StaticCredentialsAuthOptions ,
75101 options ?: StaticCredentialsAuthOptions
76102 ) {
77- this . tokenTimestamp = null ;
78103 this . user = user ;
79104 this . password = password ;
80105 this . endpoint = endpoint ;
81106 this . sslCredentials = options ?. sslCredentials ;
107+
82108 if ( typeof loggerOrOptions === 'object' && loggerOrOptions !== null && 'error' in loggerOrOptions ) {
83109 this . logger = loggerOrOptions as Logger ;
84110 } else {
85111 options = loggerOrOptions ;
86112 this . logger = getDefaultLogger ( ) ;
87113 }
114+
88115 if ( options ?. tokenRequestTimeout ) this . tokenRequestTimeout = options . tokenRequestTimeout ;
89- if ( options ?. tokenExpirationTimeout ) this . tokenExpirationTimeout = options . tokenExpirationTimeout ;
90- }
116+ if ( options ?. tokenExpirationTimeout ) this . tokenRefreshInterval = options . tokenExpirationTimeout ;
117+ if ( options ?. tokenRefreshInterval ) this . tokenRefreshInterval = options . tokenRefreshInterval ;
91118
92- private get expired ( ) {
93- return ! this . tokenTimestamp || (
94- DateTime . utc ( ) . diff ( this . tokenTimestamp ) . valueOf ( ) > this . tokenExpirationTimeout
95- ) ;
119+ if ( this . tokenRefreshInterval ) {
120+ let timer = setInterval ( ( ) => {
121+ if ( this . promise ) {
122+ return
123+ }
124+
125+ this . promise = this . updateToken ( )
126+ . then ( token => addCredentialsToMetadata ( token . value ) )
127+ . finally ( ( ) => {
128+ this . promise = null ;
129+ } )
130+ } , this . tokenRefreshInterval ) ;
131+
132+ timer . unref ( )
133+ }
96134 }
97135
98136 private async sendTokenRequest ( ) : Promise < AuthServiceResult > {
@@ -101,34 +139,52 @@ export class StaticCredentialsAuthService implements IAuthService {
101139 this . sslCredentials ,
102140 this . logger ,
103141 ) ;
142+
104143 const tokenPromise = runtimeAuthService . login ( {
105144 user : this . user ,
106145 password : this . password ,
107146 } ) ;
147+
108148 const response = await withTimeout ( tokenPromise , this . tokenRequestTimeout ) ;
109149 const result = AuthServiceResult . decode ( getOperationPayload ( response ) ) ;
110150 runtimeAuthService . destroy ( ) ;
151+
111152 return result ;
112153 }
113154
114- private async updateToken ( ) {
115- const { token} = await this . sendTokenRequest ( ) ;
116- if ( token ) {
117- this . token = token ;
118- this . tokenTimestamp = DateTime . utc ( ) ;
119- } else {
155+ private async updateToken ( ) : Promise < StaticCredentialsToken > {
156+ const { token } = await this . sendTokenRequest ( ) ;
157+ if ( ! token ) {
120158 throw new Error ( 'Received empty token from static credentials!' ) ;
121159 }
160+
161+ // Parse the JWT token to extract expiration time
162+ const [ , payload ] = token . split ( '.' ) ;
163+ const decodedPayload = JSON . parse ( Buffer . from ( payload , 'base64' ) . toString ( ) ) ;
164+
165+ this . token = {
166+ value : token ,
167+ ...decodedPayload
168+ } ;
169+
170+ return this . token !
122171 }
123172
124173 public async getAuthMetadata ( ) : Promise < grpc . Metadata > {
125- if ( this . expired || this . tokenUpdatePromise ) {
126- if ( ! this . tokenUpdatePromise ) {
127- this . tokenUpdatePromise = this . updateToken ( ) ;
128- }
129- await this . tokenUpdatePromise ;
130- this . tokenUpdatePromise = null ;
174+ if ( this . token && this . token . exp > Date . now ( ) / 1000 ) {
175+ return addCredentialsToMetadata ( this . token . value )
131176 }
132- return addCredentialsToMetadata ( this . token ) ;
177+
178+ if ( this . promise ) {
179+ return this . promise ;
180+ }
181+
182+ this . promise = this . updateToken ( )
183+ . then ( token => addCredentialsToMetadata ( token . value ) )
184+ . finally ( ( ) => {
185+ this . promise = null ;
186+ } )
187+
188+ return this . promise
133189 }
134190}
0 commit comments