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 { getOperationPayload } from "../utils/process-ydb-operation-result" ;
8
7
import * as grpc from "@grpc/grpc-js" ;
9
- import { addCredentialsToMetadata } from "./add-credentials-to-metadata" ;
8
+ import { addCredentialsToMetadata } from "./add-credentials-to-metadata" ;
10
9
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" ;
15
14
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
+ */
16
29
interface 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 */
18
31
sslCredentials ?: ISslCredentials ;
32
+
19
33
/**
20
34
* Timeout for token request in milliseconds
21
35
* @default 10 * 1000
22
36
*/
23
37
tokenRequestTimeout ?: number ;
24
- /** Expiration time for token in milliseconds
38
+
39
+ /**
40
+ * Expiration time for token in milliseconds
41
+ * @deprecated Use tokenRefreshInterval instead
25
42
* @default 6 * 60 * 60 * 1000
26
43
*/
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 ;
28
51
}
29
52
30
53
class StaticCredentialsGrpcService extends GrpcService < Ydb . Auth . V1 . AuthService > implements HasLogger {
@@ -44,16 +67,19 @@ class StaticCredentialsGrpcService extends GrpcService<Ydb.Auth.V1.AuthService>
44
67
45
68
export class StaticCredentialsAuthService implements IAuthService {
46
69
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
+
55
77
public readonly logger : Logger ;
56
78
79
+ private token : StaticCredentialsToken | null = null ;
80
+ // Mutex
81
+ private promise : Promise < grpc . Metadata > | null = null ;
82
+
57
83
constructor (
58
84
user : string ,
59
85
password : string ,
@@ -74,25 +100,37 @@ export class StaticCredentialsAuthService implements IAuthService {
74
100
loggerOrOptions ?: Logger | StaticCredentialsAuthOptions ,
75
101
options ?: StaticCredentialsAuthOptions
76
102
) {
77
- this . tokenTimestamp = null ;
78
103
this . user = user ;
79
104
this . password = password ;
80
105
this . endpoint = endpoint ;
81
106
this . sslCredentials = options ?. sslCredentials ;
107
+
82
108
if ( typeof loggerOrOptions === 'object' && loggerOrOptions !== null && 'error' in loggerOrOptions ) {
83
109
this . logger = loggerOrOptions as Logger ;
84
110
} else {
85
111
options = loggerOrOptions ;
86
112
this . logger = getDefaultLogger ( ) ;
87
113
}
114
+
88
115
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 ;
91
118
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
+ }
96
134
}
97
135
98
136
private async sendTokenRequest ( ) : Promise < AuthServiceResult > {
@@ -101,34 +139,52 @@ export class StaticCredentialsAuthService implements IAuthService {
101
139
this . sslCredentials ,
102
140
this . logger ,
103
141
) ;
142
+
104
143
const tokenPromise = runtimeAuthService . login ( {
105
144
user : this . user ,
106
145
password : this . password ,
107
146
} ) ;
147
+
108
148
const response = await withTimeout ( tokenPromise , this . tokenRequestTimeout ) ;
109
149
const result = AuthServiceResult . decode ( getOperationPayload ( response ) ) ;
110
150
runtimeAuthService . destroy ( ) ;
151
+
111
152
return result ;
112
153
}
113
154
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 ) {
120
158
throw new Error ( 'Received empty token from static credentials!' ) ;
121
159
}
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 !
122
171
}
123
172
124
173
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 )
131
176
}
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
133
189
}
134
190
}
0 commit comments