-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathldapClient.ts
More file actions
140 lines (116 loc) · 3.71 KB
/
ldapClient.ts
File metadata and controls
140 lines (116 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import { Config } from "../config.ts";
import axios, { AxiosError } from "axios";
import axiosRetry from "axios-retry";
import { Log, LoggerToUse } from "../logging.ts";
import { redisClient } from "../app.ts";
const config = Config()
export interface Entry {
cn: string,
userPrincipalName: string
}
export type SearchAllFailed = {
Succeeded: false,
Reason: "unknown" | "team_not_found"
}
export type SearchAllSucceeded = {
Succeeded: true,
entries: Entry[]
}
export type SearchAllResponse = Promise<SearchAllFailed | SearchAllSucceeded>
export async function SearchAllAsync(groupName: string): SearchAllResponse {
const cacheKey = `sot-group:${groupName}`;
const result = await redisClient.get(cacheKey);
if (result) {
LoggerToUse().ReportEvent({
Name: "CacheHit",
properties: {
"Data": groupName,
"Operation": "SearchAllAsync",
"Group": "Ldap"
}
})
return JSON.parse(result) as SearchAllResponse
}
// Make API call here
const actualResult = await ForwardSearch(groupName);
// Slightly complex for caching logic, but we don't want to cache useless results
if (actualResult.Succeeded && actualResult.entries.length > 0) {
try {
await redisClient.set(cacheKey, JSON.stringify(actualResult), {
EX: 600 // Expire after 10 minutes
});
}
catch(e) {
Log(`Error when caching results for ${groupName}: ${JSON.stringify(e)}`);
}
}
return actualResult;
}
// TODO: do not directly use axios.create from within a function like this
// it will cause a new client to be made per request.
const httpClient = axios.create();
axiosRetry(httpClient, {
retries: 2,
retryDelay: (retryCount) => {
Log(`Retry attempt: ${retryCount}`);
return retryCount * 2000;
},
retryCondition: (error) => {
if (error && error.response && error.response.status) {
return error.response.status < 200 || error.response.status > 299;
}
return true;
}
});
async function ForwardSearch(groupName: string): SearchAllResponse {
Log(`Forwarding request to '${process.env.SOURCE_PROXY}'`);
const requestUrl = `${process.env.SOURCE_PROXY}/search/${groupName}`;
Log(`Retrieving group (${groupName}) information from '${requestUrl}'`);
try {
const httpResponse = await httpClient.get(requestUrl);
Log(`Results for ${groupName}: ${JSON.stringify(httpResponse.data)}`);
if (httpResponse.status < 200 || httpResponse.status > 299) {
return {
Succeeded: false,
Reason: "unknown"
}
}
const response = httpResponse.data as SuccessResponse;
return {
Succeeded: true,
entries: response.users.map(u => {
return {
cn: u.username,
userPrincipalName: u.email
}
})
}
}
catch (e) {
Log(`Error when retrieving results for ${groupName}: ${e}`);
if (e instanceof (AxiosError)) {
const axiosError = e as AxiosError;
if (axiosError.response?.status == 404) {
return {
Succeeded: false,
Reason: "team_not_found"
}
}
}
}
return {
Succeeded: false,
Reason: "unknown"
}
}
export interface User {
username: string
email: string
}
export interface SuccessResponse {
users: User[]
}
export interface FailedResponse {
Message: string
}
export type SearchResponse = SuccessResponse | FailedResponse;