Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/publish/common/account.ts
6446 views
1
/*
2
* account.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { ensureDirSync, existsSync } from "../../deno_ral/fs.ts";
8
import { join } from "../../deno_ral/path.ts";
9
import { info } from "../../deno_ral/log.ts";
10
import * as colors from "fmt/colors";
11
import { isServerSession } from "../../core/platform.ts";
12
import { isWindows } from "../../deno_ral/platform.ts";
13
import { openUrl } from "../../core/shell.ts";
14
import { sleep } from "../../core/wait.ts";
15
import { accountsDataDir } from "./data.ts";
16
17
export interface AuthorizationHandler<Token, Ticket> {
18
name: string;
19
createTicket: () => Promise<Ticket>;
20
authorizationUrl: (ticket: Ticket) => string;
21
checkTicket: (ticket: Ticket) => Promise<Ticket>;
22
exchangeTicket: (ticket: Ticket) => Promise<Token>;
23
compareTokens?: (a: Token, b: Token) => boolean;
24
}
25
26
export async function authorizeAccessToken<
27
Token,
28
Ticket extends { id?: string; authorized?: boolean },
29
>(handler: AuthorizationHandler<Token, Ticket>): Promise<
30
Token | undefined
31
> {
32
// create ticket for authorization
33
const ticket = await handler.createTicket() as unknown as Ticket;
34
const ticketUrl = handler.authorizationUrl(ticket);
35
if (isServerSession()) {
36
info(
37
"Please authorize by opening this url: " + colors.underline(ticketUrl),
38
);
39
} else {
40
await openUrl(handler.authorizationUrl(ticket));
41
}
42
43
// poll for ticket to be authoried
44
let authorizedTicket: Ticket | undefined;
45
const checkTicket = async () => {
46
const t = await handler.checkTicket(ticket);
47
if (t.authorized) {
48
authorizedTicket = t;
49
}
50
return Boolean(t.authorized);
51
};
52
const pollingStart = Date.now();
53
const kPollingTimeout = 60 * 1000;
54
const kPollingInterval = 500;
55
while ((Date.now() - pollingStart) < kPollingTimeout) {
56
if (await checkTicket()) {
57
break;
58
}
59
await sleep(kPollingInterval);
60
}
61
if (authorizedTicket) {
62
// exechange ticket for the token
63
const accessToken = await handler.exchangeTicket(authorizedTicket);
64
65
// save the token
66
writeAccessToken<Token>(handler.name, accessToken, handler.compareTokens);
67
68
// return it
69
return accessToken;
70
} else {
71
return undefined;
72
}
73
}
74
75
export function readAccessTokens<T>(
76
provider: string,
77
): Array<T> | undefined {
78
const tokenPath = accessTokensPath(provider);
79
if (existsSync(tokenPath)) {
80
const tokens = JSON.parse(Deno.readTextFileSync(tokenPath)) as Array<T>;
81
return tokens;
82
} else {
83
return undefined;
84
}
85
}
86
87
export function writeAccessTokens<T>(
88
provider: string,
89
tokens: Array<T>,
90
) {
91
// write tokens
92
const tokensPath = accessTokensPath(provider);
93
Deno.writeTextFileSync(
94
tokensPath,
95
JSON.stringify(tokens, undefined, 2),
96
);
97
98
// set file permissions
99
if (!isWindows) {
100
Deno.chmod(tokensPath, 0o600);
101
}
102
}
103
104
export function writeAccessToken<T>(
105
provider: string,
106
token: T,
107
compareTokens?: (a: T, b: T) => boolean,
108
) {
109
let writeTokens: Array<T> | undefined;
110
111
// read existing tokens (if any)
112
writeTokens = readAccessTokens<T>(provider) || [] as Array<T>;
113
114
// update or add new
115
if (compareTokens) {
116
const updateIdx = writeTokens.findIndex((t) => compareTokens(t, token));
117
if (updateIdx !== -1) {
118
writeTokens[updateIdx] = token;
119
} else {
120
writeTokens.push(token);
121
}
122
} else {
123
writeTokens = [token];
124
}
125
126
// write tokens
127
writeAccessTokens(provider, writeTokens);
128
}
129
130
export function accessTokensPath(provider: string) {
131
const dir = join(accountsDataDir(), provider);
132
ensureDirSync(dir);
133
return join(dir, "accounts.json");
134
}
135
136