@google/clasp
Version:
Develop Apps Script Projects locally
202 lines (201 loc) • 8.07 kB
JavaScript
import { readFileSync } from 'fs';
import os from 'os';
import path from 'path';
import Debug from 'debug';
import { GoogleAuth, OAuth2Client } from 'google-auth-library';
import { google } from 'googleapis';
import { FileCredentialStore } from './file_credential_store.js';
import { LocalServerAuthorizationCodeFlow } from './localhost_auth_code_flow.js';
import { ServerlessAuthorizationCodeFlow } from './serverless_auth_code_flow.js';
const debug = Debug('clasp:auth');
export async function initAuth(options) {
var _a, _b, _c;
const authFilePath = (_a = options.authFilePath) !== null && _a !== void 0 ? _a : path.join(os.homedir(), '.clasprc.json');
const credentialStore = new FileCredentialStore(authFilePath);
debug('Initializng auth from %s', options.authFilePath);
if (options.useApplicationDefaultCredentials) {
const credentials = await createApplicationDefaultCredentials();
return {
credentials,
credentialStore,
user: (_b = options.userKey) !== null && _b !== void 0 ? _b : 'default',
};
}
const credentials = await getAuthorizedOAuth2Client(credentialStore, options.userKey);
return {
credentials,
credentialStore,
user: (_c = options.userKey) !== null && _c !== void 0 ? _c : 'default',
};
}
export async function getUserInfo(credentials) {
debug('Fetching user info');
const api = google.oauth2('v2');
try {
const res = await api.userinfo.get({ auth: credentials });
if (!res.data) {
debug('No user info returned');
return undefined;
}
return {
email: res.data.email,
id: res.data.id,
};
}
catch (err) {
debug('Error while fetching userinfo: %O', err);
return undefined;
}
}
/**
* Creates an an unauthorized oauth2 client given the client secret file. If no path is provided,
* teh default client is returned.
* @param clientSecretPath
* @returns
*/
export function getUnauthorizedOuth2Client(clientSecretPath) {
if (clientSecretPath) {
return createOauthClient(clientSecretPath);
}
return createDefaultOAuthClient();
}
/**
* Create an authorized oauth2 client from saved credentials.
* @param userKey
* @returns
*/
export async function getAuthorizedOAuth2Client(store, userKey) {
if (!userKey) {
userKey = 'default';
}
debug('Loading credentials for user %s', userKey);
const savedCredentials = await store.load(userKey);
if (!savedCredentials) {
debug('No saved credentials found.');
return undefined;
}
const client = new GoogleAuth().fromJSON(savedCredentials);
client.setCredentials(savedCredentials);
client.on('tokens', async (tokens) => {
debug('Saving refreshed token for user %s', userKey);
const refreshedCredentials = {
...savedCredentials,
expiry_date: tokens.expiry_date,
access_token: tokens.access_token,
id_token: tokens.access_token,
};
await store.save(userKey, refreshedCredentials);
});
return client;
}
/**
* Requests authorization to manage Apps Script projects.
* @param {boolean} useLocalhost Uses a local HTTP server if true. Manual entry o.w.
* @param {ClaspCredentials?} creds An optional credentials object.
* @param {string[]} [scopes=[]] List of OAuth scopes to authorize.
* @param {number?} redirectPort Optional custom port for the local HTTP server during the authorization process.
* If not specified, a random available port will be used.
*/
export async function authorize(options) {
let flow;
if (options.noLocalServer) {
debug('Starting auth with serverless flow');
flow = new ServerlessAuthorizationCodeFlow(options.oauth2Client);
}
else {
debug('Starting auth with local server flow');
flow = new LocalServerAuthorizationCodeFlow(options.oauth2Client);
}
const client = await flow.authorize(options.scopes);
await saveOauthClientCredentials(options.store, options.userKey, client);
debug('Auth complete');
return client;
}
async function saveOauthClientCredentials(store, userKey, oauth2Client) {
var _a, _b;
const savedCredentials = {
client_id: oauth2Client._clientId,
client_secret: oauth2Client._clientSecret,
type: 'authorized_user',
refresh_token: (_a = oauth2Client.credentials.refresh_token) !== null && _a !== void 0 ? _a : undefined,
access_token: (_b = oauth2Client.credentials.access_token) !== null && _b !== void 0 ? _b : undefined,
};
oauth2Client.on('tokens', async (tokens) => {
const refreshedCredentials = {
...savedCredentials,
expiry_date: tokens.expiry_date,
access_token: tokens.access_token,
id_token: tokens.access_token,
};
debug('Saving refreshed credentials for user %s', userKey);
await store.save(userKey, refreshedCredentials);
});
debug('Saving credentials for user %s', userKey);
await store.save(userKey, savedCredentials);
}
/**
* Creates an aunthorized oauth2 client with the given credentials
* @param clientSecretPath
* @returns
*/
function createOauthClient(clientSecretPath) {
debug('Creating new oauth client from %s', clientSecretPath);
if (!clientSecretPath) {
throw new Error('Invalid credentials');
}
const contents = readFileSync(clientSecretPath);
const keyFile = JSON.parse(contents.toString());
const keys = keyFile.installed || keyFile.web;
if (!keys.redirect_uris || keys.redirect_uris.length === 0) {
throw new Error('Invalid redirect URL');
}
const redirectUrl = keys.redirect_uris.find((uri) => new URL(uri).hostname === 'localhost');
if (!redirectUrl) {
throw new Error('No localhost redirect URL found');
}
// create an oAuth client to authorize the API call
const client = new OAuth2Client({
clientId: keys.client_id,
clientSecret: keys.client_secret,
redirectUri: redirectUrl,
});
debug('Created built-in oauth client, id: %s', client._clientId);
return client;
}
/**
* Creates an aunthorized oauth2 client using the default id & secret.
* @param clientSecretPath
* @returns
*/
function createDefaultOAuthClient() {
// Default client
const client = new OAuth2Client({
clientId: '1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com',
clientSecret: 'v6V3fKV_zWU7iw1DrpO1rknX',
redirectUri: 'http://localhost',
});
debug('Created built-in oauth client, id: %s', client._clientId);
return client;
}
export async function createApplicationDefaultCredentials() {
const defaultCreds = await new GoogleAuth({
scopes: [
'https://www.googleapis.com/auth/script.deployments', // Apps Script deployments
'https://www.googleapis.com/auth/script.projects', // Apps Script management
'https://www.googleapis.com/auth/script.webapp.deploy', // Apps Script Web Apps
'https://www.googleapis.com/auth/drive.metadata.readonly', // Drive metadata
'https://www.googleapis.com/auth/drive.file', // Create Drive files
'https://www.googleapis.com/auth/service.management', // Cloud Project Service Management API
'https://www.googleapis.com/auth/logging.read', // StackDriver logs
'https://www.googleapis.com/auth/userinfo.email', // User email address
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/cloud-platform',
],
}).getClient();
// Remove this check after https://github.com/googleapis/google-auth-library-nodejs/issues/1677 fixed
if (defaultCreds instanceof OAuth2Client) {
debug('Created service account credentials, id: %s', defaultCreds._clientId);
return defaultCreds;
}
return undefined;
}