This repository has been archived by the owner on Aug 28, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 231
/
nodefetchclient.ts
146 lines (118 loc) · 4.96 KB
/
nodefetchclient.ts
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
141
142
143
144
145
146
declare var global: any;
declare var require: (path: string) => any;
const nodeFetch = require("node-fetch");
const u: any = require("url");
import { HttpClientImpl } from "./httpclient";
import { Util } from "../utils/util";
import { AuthUrlException } from "../utils/exceptions";
export interface AuthToken {
token_type: string;
expires_in: string;
not_before: string;
expires_on: string;
resource: string;
access_token: string;
}
/**
* Fetch client for use within nodejs, requires you register a client id and secret with app only permissions
*/
export class NodeFetchClient implements HttpClientImpl {
private static SharePointServicePrincipal = "00000003-0000-0ff1-ce00-000000000000";
private token: AuthToken = null;
constructor(public siteUrl: string, private _clientId: string, private _clientSecret: string, private _realm = "") {
// here we set the globals for fetch things when this client is instantiated
global.Headers = nodeFetch.Headers;
global.Request = nodeFetch.Request;
global.Response = nodeFetch.Response;
global._spPageContextInfo = {
webAbsoluteUrl: siteUrl,
};
}
public fetch(url: string, options: any): Promise<Response> {
if (!Util.isUrlAbsolute(url)) {
url = Util.combinePaths(this.siteUrl, url);
}
return this.getAddInOnlyAccessToken().then(token => {
options.headers.set("Authorization", `Bearer ${token.access_token}`);
return nodeFetch(url, options);
});
}
/**
* Gets an add-in only authentication token based on the supplied site url, client id and secret
*/
public getAddInOnlyAccessToken(): Promise<AuthToken> {
return new Promise<AuthToken>((resolve, reject) => {
if (this.token !== null && new Date() < this.toDate(this.token.expires_on)) {
resolve(this.token);
} else {
this.getRealm().then((realm: string) => {
const resource = this.getFormattedPrincipal(NodeFetchClient.SharePointServicePrincipal, u.parse(this.siteUrl).hostname, realm);
const formattedClientId = this.getFormattedPrincipal(this._clientId, "", realm);
this.getAuthUrl(realm).then((authUrl: string) => {
const body: string[] = [];
body.push("grant_type=client_credentials");
body.push(`client_id=${formattedClientId}`);
body.push(`client_secret=${encodeURIComponent(this._clientSecret)}`);
body.push(`resource=${resource}`);
nodeFetch(authUrl, {
body: body.join("&"),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
}).then((r: Response) => r.json()).then((tok: AuthToken) => {
this.token = tok;
resolve(this.token);
});
});
}).catch(e => reject(e));
}
});
}
private getRealm(): Promise<string> {
return new Promise(resolve => {
if (this._realm.length > 0) {
resolve(this._realm);
}
const url = Util.combinePaths(this.siteUrl, "vti_bin/client.svc");
nodeFetch(url, {
"headers": {
"Authorization": "Bearer ",
},
"method": "POST",
}).then((r: Response) => {
const data: string = r.headers.get("www-authenticate");
const index = data.indexOf("Bearer realm=\"");
this._realm = data.substring(index + 14, index + 50);
resolve(this._realm);
});
});
}
private getAuthUrl(realm: string): Promise<string> {
const url = `https://accounts.accesscontrol.windows.net/metadata/json/1?realm=${realm}`;
return nodeFetch(url).then((r: Response) => r.json()).then((json: { endpoints: { protocol: string, location: string }[] }) => {
const eps = json.endpoints.filter(ep => ep.protocol === "OAuth2");
if (eps.length > 0) {
return eps[0].location;
}
throw new AuthUrlException(json);
});
}
private getFormattedPrincipal(principalName: string, hostName: string, realm: string): string {
let resource = principalName;
if (hostName !== null && hostName !== "") {
resource += "/" + hostName;
}
resource += "@" + realm;
return resource;
}
private toDate(epoch: string): Date {
let tmp = parseInt(epoch, 10);
if (tmp < 10000000000) {
tmp *= 1000;
}
const d = new Date();
d.setTime(tmp);
return d;
}
}