1
- import { Ydb } from "ydb-sdk-proto" ;
1
+ import { Ydb } from "ydb-sdk-proto" ;
2
2
import 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 { DateTime } from "luxon" ;
7
+ import { getOperationPayload } from "../utils/process-ydb-operation-result" ;
8
8
import * as grpc from "@grpc/grpc-js" ;
9
- import { addCredentialsToMetadata } from "./add-credentials-to-metadata" ;
9
+ import { addCredentialsToMetadata } from "./add-credentials-to-metadata" ;
10
10
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" ;
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" ;
15
15
16
+ /**
17
+ * Static credentials token.
18
+ */
19
+ export type StaticCredentialsToken = {
20
+ value : string
21
+ aud : string [ ]
22
+ exp : number
23
+ iat : number
24
+ sub : string
25
+ }
26
+
27
+ /**
28
+ * Interface for options used in static credentials authentication.
29
+ */
16
30
interface StaticCredentialsAuthOptions {
17
- /** Custom ssl sertificates . If you use it in driver, you must use it here too */
31
+ /** Custom SSL certificates . If you use it in driver, you must use it here too */
18
32
sslCredentials ?: ISslCredentials ;
33
+
19
34
/**
20
35
* Timeout for token request in milliseconds
21
36
* @default 10 * 1000
22
37
*/
23
38
tokenRequestTimeout ?: number ;
24
- /** Expiration time for token in milliseconds
39
+
40
+ /**
41
+ * Expiration time for token in milliseconds
42
+ * @deprecated Use tokenRefreshInterval instead
25
43
* @default 6 * 60 * 60 * 1000
26
44
*/
27
- tokenExpirationTimeout ?: number
45
+ tokenExpirationTimeout ?: number ;
46
+
47
+ /**
48
+ * Time interval in milliseconds after which the token will be refreshed.
49
+ * When specified, token refresh is based on this timer rather than the token's exp field.
50
+ */
51
+ tokenRefreshInterval ?: number ;
28
52
}
29
53
30
54
class StaticCredentialsGrpcService extends GrpcService < Ydb . Auth . V1 . AuthService > implements HasLogger {
@@ -44,16 +68,19 @@ class StaticCredentialsGrpcService extends GrpcService<Ydb.Auth.V1.AuthService>
44
68
45
69
export class StaticCredentialsAuthService implements IAuthService {
46
70
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 ;
71
+ private readonly tokenRefreshInterval : number | null = null ;
72
+
73
+ private readonly user : string ;
74
+ private readonly password : string ;
75
+ private readonly endpoint : string ;
76
+ private readonly sslCredentials : ISslCredentials | undefined ;
77
+
55
78
public readonly logger : Logger ;
56
79
80
+ private token : StaticCredentialsToken | null = null ;
81
+ // Mutex
82
+ private promise : Promise < grpc . Metadata > | null = null ;
83
+
57
84
constructor (
58
85
user : string ,
59
86
password : string ,
@@ -74,25 +101,37 @@ export class StaticCredentialsAuthService implements IAuthService {
74
101
loggerOrOptions ?: Logger | StaticCredentialsAuthOptions ,
75
102
options ?: StaticCredentialsAuthOptions
76
103
) {
77
- this . tokenTimestamp = null ;
78
104
this . user = user ;
79
105
this . password = password ;
80
106
this . endpoint = endpoint ;
81
107
this . sslCredentials = options ?. sslCredentials ;
108
+
82
109
if ( typeof loggerOrOptions === 'object' && loggerOrOptions !== null && 'error' in loggerOrOptions ) {
83
110
this . logger = loggerOrOptions as Logger ;
84
111
} else {
85
112
options = loggerOrOptions ;
86
113
this . logger = getDefaultLogger ( ) ;
87
114
}
115
+
88
116
if ( options ?. tokenRequestTimeout ) this . tokenRequestTimeout = options . tokenRequestTimeout ;
89
- if ( options ?. tokenExpirationTimeout ) this . tokenExpirationTimeout = options . tokenExpirationTimeout ;
90
- }
117
+ if ( options ?. tokenExpirationTimeout ) this . tokenRefreshInterval = options . tokenExpirationTimeout ;
118
+ if ( options ?. tokenRefreshInterval ) this . tokenRefreshInterval = options . tokenRefreshInterval ;
91
119
92
- private get expired ( ) {
93
- return ! this . tokenTimestamp || (
94
- DateTime . utc ( ) . diff ( this . tokenTimestamp ) . valueOf ( ) > this . tokenExpirationTimeout
95
- ) ;
120
+ if ( this . tokenRefreshInterval ) {
121
+ let timer = setInterval ( ( ) => {
122
+ if ( this . promise ) {
123
+ return
124
+ }
125
+
126
+ this . promise = this . updateToken ( )
127
+ . then ( token => addCredentialsToMetadata ( token . value ) )
128
+ . finally ( ( ) => {
129
+ this . promise = null ;
130
+ } )
131
+ } , this . tokenRefreshInterval ) ;
132
+
133
+ timer . unref ( )
134
+ }
96
135
}
97
136
98
137
private async sendTokenRequest ( ) : Promise < AuthServiceResult > {
@@ -101,34 +140,52 @@ export class StaticCredentialsAuthService implements IAuthService {
101
140
this . sslCredentials ,
102
141
this . logger ,
103
142
) ;
143
+
104
144
const tokenPromise = runtimeAuthService . login ( {
105
145
user : this . user ,
106
146
password : this . password ,
107
147
} ) ;
148
+
108
149
const response = await withTimeout ( tokenPromise , this . tokenRequestTimeout ) ;
109
150
const result = AuthServiceResult . decode ( getOperationPayload ( response ) ) ;
110
151
runtimeAuthService . destroy ( ) ;
152
+
111
153
return result ;
112
154
}
113
155
114
- private async updateToken ( ) {
115
- const { token} = await this . sendTokenRequest ( ) ;
116
- if ( token ) {
117
- this . token = token ;
118
- this . tokenTimestamp = DateTime . utc ( ) ;
119
- } else {
156
+ private async updateToken ( ) : Promise < StaticCredentialsToken > {
157
+ const { token } = await this . sendTokenRequest ( ) ;
158
+ if ( ! token ) {
120
159
throw new Error ( 'Received empty token from static credentials!' ) ;
121
160
}
161
+
162
+ // Parse the JWT token to extract expiration time
163
+ const [ , payload ] = token . split ( '.' ) ;
164
+ const decodedPayload = JSON . parse ( Buffer . from ( payload , 'base64' ) . toString ( ) ) ;
165
+
166
+ this . token = {
167
+ value : token ,
168
+ ...decodedPayload
169
+ } ;
170
+
171
+ return this . token !
122
172
}
123
173
124
174
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 ;
175
+ if ( this . token && this . token . exp > Date . now ( ) / 1000 ) {
176
+ return addCredentialsToMetadata ( this . token . value )
131
177
}
132
- return addCredentialsToMetadata ( this . token ) ;
178
+
179
+ if ( this . promise ) {
180
+ return this . promise ;
181
+ }
182
+
183
+ this . promise = this . updateToken ( )
184
+ . then ( token => addCredentialsToMetadata ( token . value ) )
185
+ . finally ( ( ) => {
186
+ this . promise = null ;
187
+ } )
188
+
189
+ return this . promise
133
190
}
134
191
}
0 commit comments