UNPKG

3 kBPlain TextView Raw
1/*
2 * Copyright 2019 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
18import { Call } from './call-stream';
19import { Channel } from './channel';
20import { BaseFilter, Filter, FilterFactory } from './filter';
21import { Metadata } from './metadata';
22import { Status } from './constants';
23import { splitHostPort } from './uri-parser';
24import { ServiceError } from './call';
25
26export class CallCredentialsFilter extends BaseFilter implements Filter {
27 private serviceUrl: string;
28 constructor(
29 private readonly channel: Channel,
30 private readonly stream: Call
31 ) {
32 super();
33 this.channel = channel;
34 this.stream = stream;
35 const splitPath: string[] = stream.getMethod().split('/');
36 let serviceName = '';
37 /* The standard path format is "/{serviceName}/{methodName}", so if we split
38 * by '/', the first item should be empty and the second should be the
39 * service name */
40 if (splitPath.length >= 2) {
41 serviceName = splitPath[1];
42 }
43 const hostname = splitHostPort(stream.getHost())?.host ?? 'localhost';
44 /* Currently, call credentials are only allowed on HTTPS connections, so we
45 * can assume that the scheme is "https" */
46 this.serviceUrl = `https://${hostname}/${serviceName}`;
47 }
48
49 async sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
50 const credentials = this.stream.getCredentials();
51 const credsMetadata = credentials.generateMetadata({
52 service_url: this.serviceUrl,
53 });
54 const resultMetadata = await metadata;
55 try {
56 resultMetadata.merge(await credsMetadata);
57 } catch (error) {
58 this.stream.cancelWithStatus(
59 Status.UNAUTHENTICATED,
60 `Failed to retrieve auth metadata with error: ${error.message}`
61 );
62 return Promise.reject<Metadata>('Failed to retrieve auth metadata');
63 }
64 if (resultMetadata.get('authorization').length > 1) {
65 this.stream.cancelWithStatus(
66 Status.INTERNAL,
67 '"authorization" metadata cannot have multiple values'
68 );
69 return Promise.reject<Metadata>(
70 '"authorization" metadata cannot have multiple values'
71 );
72 }
73 return resultMetadata;
74 }
75}
76
77export class CallCredentialsFilterFactory
78 implements FilterFactory<CallCredentialsFilter> {
79 constructor(private readonly channel: Channel) {
80 this.channel = channel;
81 }
82
83 createFilter(callStream: Call): CallCredentialsFilter {
84 return new CallCredentialsFilter(this.channel, callStream);
85 }
86}