UNPKG

@google/clasp

Version:

Develop Apps Script Projects locally

138 lines (137 loc) 5.79 kB
// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This file implements the `CredentialStore` interface, providing a file-based // mechanism for storing and managing user credentials. It handles different // file formats for compatibility with older versions of clasp. import fs from 'fs'; function hasLegacyLocalCredentials(store) { return store.token && store.oauth2ClientSettings; } function hasLegacyGlobalCredentials(store) { return !!store.access_token; } /** * Implements the `CredentialStore` interface using a local JSON file. * This class handles saving, loading, and deleting OAuth 2.0 credentials * for different users. It also supports migrating credentials from older * clasp file formats. */ export class FileCredentialStore { constructor(filePath) { this.filePath = filePath; } /** * Saves credentials for a given user. * If credentials are provided as undefined, it effectively removes the user's credentials. * @param {string} user - The identifier for the user. * @param {StoredCredential | undefined} credentials - The credentials to save, or undefined to clear. * @returns {Promise<void>} */ async save(user, credentials) { const store = this.readFile(); if (!store.tokens) { store.tokens = {}; } store.tokens[user] = credentials; this.writeFile(store); } /** * Deletes credentials for a specific user. * If deleting the 'default' user, it also cleans up legacy credential formats. * @param {string} user - The identifier for the user whose credentials are to be deleted. * @returns {Promise<void>} */ async delete(user) { let store = this.readFile(); if (!store.tokens) { store.tokens = {}; } store.tokens[user] = undefined; if (user === 'default') { // If the 'default' user's token is deleted, we also clean up any potential // top-level V1 credential keys to ensure a clean state and prevent // V1 credentials from being loaded unintentionally after a V3 'default' delete. store = { tokens: store.tokens, // Keep other named tokens if they exist }; } this.writeFile(store); } /** * Deletes all stored credentials by clearing the tokens map. * @returns {Promise<void>} */ async deleteAll() { await this.writeFile({ tokens: {}, }); } /** * Loads credentials for a given user. * It supports loading credentials from the current format as well as * attempting to load from legacy V1 local and global file formats * if the user is 'default' and no V3 credentials are found. * @param {string} user - The identifier for the user. * @returns {Promise<StoredCredential | null>} The stored credentials if found, otherwise null. */ async load(user) { var _a, _b, _c; const store = this.readFile(); const credentials = (_a = store.tokens) === null || _a === void 0 ? void 0 : _a[user]; if (credentials) { return credentials; // Modern V3 token found for the user. } // The following logic attempts to load legacy V1 credentials // ONLY if the requested user is 'default' and no V3 'default' token was found. if (user !== 'default') { return null; // For non-default users, only V3 tokens are considered. } // Check for V1 local file format (usually from older .clasprc.json in project root) if (hasLegacyLocalCredentials(store)) { // Convert V1 local format to StoredCredential format. return { type: 'authorized_user', ...store.token, // Spread V1 token properties client_id: (_b = store.oauth2ClientSettings) === null || _b === void 0 ? void 0 : _b.clientId, client_secret: (_c = store.oauth2ClientSettings) === null || _c === void 0 ? void 0 : _c.clientSecret, }; } // Check for V1 global file format (usually from older ~/.clasprc.json) if (hasLegacyGlobalCredentials(store)) { // Convert V1 global format to StoredCredential format. // Note: Default client_id and client_secret are used here as global V1 didn't store them. return { type: 'authorized_user', access_token: store.access_token, // Map V1 fields refresh_token: store.refresh_token, expiry_date: store.exprity_date, token_type: store.token_type, client_id: '1072944905499-vm2v2i5dvn0a0d2o4ca36i1vge8cvbn0.apps.googleusercontent.com', client_secret: 'v6V3fKV_zWU7iw1DrpO1rknX', }; } return null; } readFile() { if (fs.existsSync(this.filePath)) { // TODO - use promises const content = fs.readFileSync(this.filePath, { encoding: 'utf8' }); return JSON.parse(content); } return {}; } writeFile(store) { fs.writeFileSync(this.filePath, JSON.stringify(store, null, 2)); } }