Skip to content

Commit 78f194b

Browse files
committed
grpc-js: Add getAuthContext call method
1 parent 6c7abfe commit 78f194b

12 files changed

+147
-0
lines changed

packages/grpc-js/src/auth-context.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2025 gRPC authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
import { PeerCertificate } from "tls";
19+
20+
export interface AuthContext {
21+
transportSecurityType?: string;
22+
sslPeerCertificate?: PeerCertificate;
23+
}

packages/grpc-js/src/call-interface.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*
1616
*/
1717

18+
import { AuthContext } from './auth-context';
1819
import { CallCredentials } from './call-credentials';
1920
import { Status } from './constants';
2021
import { Deadline } from './deadline';
@@ -170,6 +171,7 @@ export interface Call {
170171
halfClose(): void;
171172
getCallNumber(): number;
172173
setCredentials(credentials: CallCredentials): void;
174+
getAuthContext(): AuthContext | null;
173175
}
174176

175177
export interface DeadlineInfoProvider {

packages/grpc-js/src/call.ts

+18
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { EmitterAugmentation1 } from './events';
2424
import { Metadata } from './metadata';
2525
import { ObjectReadable, ObjectWritable, WriteCallback } from './object-stream';
2626
import { InterceptingCallInterface } from './client-interceptors';
27+
import { AuthContext } from './auth-context';
2728

2829
/**
2930
* A type extending the built-in Error object with additional fields.
@@ -37,6 +38,7 @@ export type SurfaceCall = {
3738
call?: InterceptingCallInterface;
3839
cancel(): void;
3940
getPeer(): string;
41+
getAuthContext(): AuthContext | null;
4042
} & EmitterAugmentation1<'metadata', Metadata> &
4143
EmitterAugmentation1<'status', StatusObject> &
4244
EventEmitter;
@@ -100,6 +102,10 @@ export class ClientUnaryCallImpl
100102
getPeer(): string {
101103
return this.call?.getPeer() ?? 'unknown';
102104
}
105+
106+
getAuthContext(): AuthContext | null {
107+
return this.call?.getAuthContext() ?? null;
108+
}
103109
}
104110

105111
export class ClientReadableStreamImpl<ResponseType>
@@ -119,6 +125,10 @@ export class ClientReadableStreamImpl<ResponseType>
119125
return this.call?.getPeer() ?? 'unknown';
120126
}
121127

128+
getAuthContext(): AuthContext | null {
129+
return this.call?.getAuthContext() ?? null;
130+
}
131+
122132
_read(_size: number): void {
123133
this.call?.startRead();
124134
}
@@ -141,6 +151,10 @@ export class ClientWritableStreamImpl<RequestType>
141151
return this.call?.getPeer() ?? 'unknown';
142152
}
143153

154+
getAuthContext(): AuthContext | null {
155+
return this.call?.getAuthContext() ?? null;
156+
}
157+
144158
_write(chunk: RequestType, encoding: string, cb: WriteCallback) {
145159
const context: MessageContext = {
146160
callback: cb,
@@ -178,6 +192,10 @@ export class ClientDuplexStreamImpl<RequestType, ResponseType>
178192
return this.call?.getPeer() ?? 'unknown';
179193
}
180194

195+
getAuthContext(): AuthContext | null {
196+
return this.call?.getAuthContext() ?? null;
197+
}
198+
181199
_read(_size: number): void {
182200
this.call?.startRead();
183201
}

packages/grpc-js/src/client-interceptors.ts

+8
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { Channel } from './channel';
3434
import { CallOptions } from './client';
3535
import { ClientMethodDefinition } from './make-client';
3636
import { getErrorMessage } from './error';
37+
import { AuthContext } from './auth-context';
3738

3839
/**
3940
* Error class associated with passing both interceptors and interceptor
@@ -198,6 +199,7 @@ export interface InterceptingCallInterface {
198199
sendMessage(message: any): void;
199200
startRead(): void;
200201
halfClose(): void;
202+
getAuthContext(): AuthContext | null;
201203
}
202204

203205
export class InterceptingCall implements InterceptingCallInterface {
@@ -338,6 +340,9 @@ export class InterceptingCall implements InterceptingCallInterface {
338340
}
339341
});
340342
}
343+
getAuthContext(): AuthContext | null {
344+
return this.nextCall.getAuthContext();
345+
}
341346
}
342347

343348
function getCall(channel: Channel, path: string, options: CallOptions): Call {
@@ -427,6 +432,9 @@ class BaseInterceptingCall implements InterceptingCallInterface {
427432
halfClose(): void {
428433
this.call.halfClose();
429434
}
435+
getAuthContext(): AuthContext | null {
436+
return this.call.getAuthContext();
437+
}
430438
}
431439

432440
/**

packages/grpc-js/src/load-balancing-call.ts

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { splitHostPort } from './uri-parser';
3535
import * as logging from './logging';
3636
import { restrictControlPlaneStatusCode } from './control-plane-status';
3737
import * as http2 from 'http2';
38+
import { AuthContext } from './auth-context';
3839

3940
const TRACER_NAME = 'load_balancing_call';
4041

@@ -375,4 +376,12 @@ export class LoadBalancingCall implements Call, DeadlineInfoProvider {
375376
getCallNumber(): number {
376377
return this.callNumber;
377378
}
379+
380+
getAuthContext(): AuthContext | null {
381+
if (this.child) {
382+
return this.child.getAuthContext();
383+
} else {
384+
return null;
385+
}
386+
}
378387
}

packages/grpc-js/src/resolving-call.ts

+9
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { InternalChannel } from './internal-channel';
3737
import { Metadata } from './metadata';
3838
import * as logging from './logging';
3939
import { restrictControlPlaneStatusCode } from './control-plane-status';
40+
import { AuthContext } from './auth-context';
4041

4142
const TRACER_NAME = 'resolving_call';
4243

@@ -367,4 +368,12 @@ export class ResolvingCall implements Call {
367368
getCallNumber(): number {
368369
return this.callNumber;
369370
}
371+
372+
getAuthContext(): AuthContext | null {
373+
if (this.child) {
374+
return this.child.getAuthContext();
375+
} else {
376+
return null;
377+
}
378+
}
370379
}

packages/grpc-js/src/retrying-call.ts

+8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
StatusObjectWithProgress,
3636
} from './load-balancing-call';
3737
import { InternalChannel } from './internal-channel';
38+
import { AuthContext } from './auth-context';
3839

3940
const TRACER_NAME = 'retrying_call';
4041

@@ -859,4 +860,11 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
859860
getHost(): string {
860861
return this.host;
861862
}
863+
getAuthContext(): AuthContext | null {
864+
if (this.committedCallIndex !== null) {
865+
return this.underlyingCalls[this.committedCallIndex].call.getAuthContext();
866+
} else {
867+
return null;
868+
}
869+
}
862870
}

packages/grpc-js/src/server-call.ts

+18
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import type { ObjectReadable, ObjectWritable } from './object-stream';
2525
import type { StatusObject, PartialStatusObject } from './call-interface';
2626
import type { Deadline } from './deadline';
2727
import type { ServerInterceptingCallInterface } from './server-interceptors';
28+
import { AuthContext } from './auth-context';
2829

2930
export type ServerStatusResponse = Partial<StatusObject>;
3031

@@ -38,6 +39,7 @@ export type ServerSurfaceCall = {
3839
getDeadline(): Deadline;
3940
getPath(): string;
4041
getHost(): string;
42+
getAuthContext(): AuthContext;
4143
} & EventEmitter;
4244

4345
export type ServerUnaryCall<RequestType, ResponseType> = ServerSurfaceCall & {
@@ -114,6 +116,10 @@ export class ServerUnaryCallImpl<RequestType, ResponseType>
114116
getHost(): string {
115117
return this.call.getHost();
116118
}
119+
120+
getAuthContext(): AuthContext {
121+
return this.call.getAuthContext();
122+
}
117123
}
118124

119125
export class ServerReadableStreamImpl<RequestType, ResponseType>
@@ -154,6 +160,10 @@ export class ServerReadableStreamImpl<RequestType, ResponseType>
154160
getHost(): string {
155161
return this.call.getHost();
156162
}
163+
164+
getAuthContext(): AuthContext {
165+
return this.call.getAuthContext();
166+
}
157167
}
158168

159169
export class ServerWritableStreamImpl<RequestType, ResponseType>
@@ -203,6 +213,10 @@ export class ServerWritableStreamImpl<RequestType, ResponseType>
203213
return this.call.getHost();
204214
}
205215

216+
getAuthContext(): AuthContext {
217+
return this.call.getAuthContext();
218+
}
219+
206220
_write(
207221
chunk: ResponseType,
208222
encoding: string,
@@ -276,6 +290,10 @@ export class ServerDuplexStreamImpl<RequestType, ResponseType>
276290
return this.call.getHost();
277291
}
278292

293+
getAuthContext(): AuthContext {
294+
return this.call.getAuthContext();
295+
}
296+
279297
_read(size: number) {
280298
this.call.startRead();
281299
}

packages/grpc-js/src/server-interceptors.ts

+19
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import * as zlib from 'zlib';
3333
import { StreamDecoder } from './stream-decoder';
3434
import { CallEventTracker } from './transport';
3535
import * as logging from './logging';
36+
import { AuthContext } from './auth-context';
37+
import { TLSSocket } from 'tls';
3638

3739
const TRACER_NAME = 'server_call';
3840

@@ -332,6 +334,10 @@ export interface ServerInterceptingCallInterface {
332334
* Return the host requested by the client in the ":authority" header.
333335
*/
334336
getHost(): string;
337+
/**
338+
* Return the auth context of the connection the call is associated with.
339+
*/
340+
getAuthContext(): AuthContext;
335341
}
336342

337343
export class ServerInterceptingCall implements ServerInterceptingCallInterface {
@@ -440,6 +446,9 @@ export class ServerInterceptingCall implements ServerInterceptingCallInterface {
440446
getHost(): string {
441447
return this.nextCall.getHost();
442448
}
449+
getAuthContext(): AuthContext {
450+
return this.nextCall.getAuthContext();
451+
}
443452
}
444453

445454
export interface ServerInterceptor {
@@ -971,6 +980,16 @@ export class BaseServerInterceptingCall
971980
getHost(): string {
972981
return this.host;
973982
}
983+
getAuthContext(): AuthContext {
984+
if (this.stream.session?.socket instanceof TLSSocket) {
985+
return {
986+
transportSecurityType: 'ssl',
987+
sslPeerCertificate: this.stream.session.socket.getPeerCertificate()
988+
}
989+
} else {
990+
return {};
991+
}
992+
}
974993
}
975994

976995
export function getServerInterceptingCall(

packages/grpc-js/src/subchannel-call.ts

+6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
WriteCallback,
3131
} from './call-interface';
3232
import { CallEventTracker, Transport } from './transport';
33+
import { AuthContext } from './auth-context';
3334

3435
const TRACER_NAME = 'subchannel_call';
3536

@@ -71,6 +72,7 @@ export interface SubchannelCall {
7172
halfClose(): void;
7273
getCallNumber(): number;
7374
getDeadlineInfo(): string[];
75+
getAuthContext(): AuthContext;
7476
}
7577

7678
export interface StatusObjectWithRstCode extends StatusObject {
@@ -556,6 +558,10 @@ export class Http2SubchannelCall implements SubchannelCall {
556558
return this.callId;
557559
}
558560

561+
getAuthContext(): AuthContext {
562+
return this.transport.getAuthContext();
563+
}
564+
559565
startRead() {
560566
/* If the stream has ended with an error, we should not emit any more
561567
* messages and we should communicate that the stream has ended */

packages/grpc-js/src/transport.ts

+17
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
import { Metadata } from './metadata';
5252
import { getNextCallNumber } from './call-number';
5353
import { Socket } from 'net';
54+
import { AuthContext } from './auth-context';
5455

5556
const TRACER_NAME = 'transport';
5657
const FLOW_CONTROL_TRACER_NAME = 'transport_flowctrl';
@@ -83,6 +84,7 @@ export interface Transport {
8384
getChannelzRef(): SocketRef;
8485
getPeerName(): string;
8586
getOptions(): ChannelOptions;
87+
getAuthContext(): AuthContext;
8688
createCall(
8789
metadata: Metadata,
8890
host: string,
@@ -129,6 +131,8 @@ class Http2Transport implements Transport {
129131

130132
private disconnectHandled = false;
131133

134+
private authContext: AuthContext;
135+
132136
// Channelz info
133137
private channelzRef: SocketRef;
134138
private readonly channelzEnabled: boolean = true;
@@ -254,6 +258,15 @@ class Http2Transport implements Transport {
254258
if (this.keepaliveWithoutCalls) {
255259
this.maybeStartKeepalivePingTimer();
256260
}
261+
262+
if (session.socket instanceof TLSSocket) {
263+
this.authContext = {
264+
transportSecurityType: 'ssl',
265+
sslPeerCertificate: session.socket.getPeerCertificate()
266+
};
267+
} else {
268+
this.authContext = {};
269+
}
257270
}
258271

259272
private getChannelzInfo(): SocketInfo {
@@ -622,6 +635,10 @@ class Http2Transport implements Transport {
622635
return this.options;
623636
}
624637

638+
getAuthContext(): AuthContext {
639+
return this.authContext;
640+
}
641+
625642
shutdown() {
626643
this.session.close();
627644
unregisterChannelzRef(this.channelzRef);

0 commit comments

Comments
 (0)