@nu-art/google-services-backend 0.400.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './modules/ModuleBE_GcpManager.js';
2
+ export * from './modules/ModuleBE_GoogleContacts.js';
3
+ export * from './modules/ModuleBE_Auth.js';
4
+ export * from './modules/ModuleBE_GooglePubSub.js';
package/index.js ADDED
@@ -0,0 +1,22 @@
1
+ /*
2
+ * Permissions management system, define access level for each of
3
+ * your server apis, and restrict users by giving them access levels
4
+ *
5
+ * Copyright (C) 2020 Yair bcm
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ export * from './modules/ModuleBE_GcpManager.js';
20
+ export * from './modules/ModuleBE_GoogleContacts.js';
21
+ export * from './modules/ModuleBE_Auth.js';
22
+ export * from './modules/ModuleBE_GooglePubSub.js';
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Created by tacb0ss on 07/05/2018.
3
+ */
4
+ import { Module } from '@nu-art/ts-common';
5
+ import { google } from 'googleapis';
6
+ import { GoogleAuth, JWTInput } from 'google-auth-library';
7
+ type AuthClient_ = typeof google.auth.getClient;
8
+ type ClientOptions = NonNullable<Parameters<AuthClient_>[0]>['clientOptions'];
9
+ type AuthModuleConfig = {
10
+ auth: {
11
+ [k: string]: JWT_Input | string;
12
+ };
13
+ };
14
+ export type JWT_Input = JWTInput;
15
+ export declare class ModuleBE_Auth_Class extends Module<AuthModuleConfig> {
16
+ constructor();
17
+ getAuth(authKey: string, scopes: string[], clientOptions?: ClientOptions): ({
18
+ auth: GoogleAuth;
19
+ });
20
+ getAuthConfig(authKey: string): string | JWTInput;
21
+ getJWT(authKey: string, scopes: string[]): Promise<import("google-auth-library").Credentials>;
22
+ }
23
+ export declare const ModuleBE_Auth: ModuleBE_Auth_Class;
24
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Created by tacb0ss on 07/05/2018.
3
+ */
4
+ import { ImplementationMissingException, Module, NotImplementedYetException } from '@nu-art/ts-common';
5
+ import { GoogleAuth, JWT, } from 'google-auth-library';
6
+ export class ModuleBE_Auth_Class extends Module {
7
+ constructor() {
8
+ super();
9
+ this.setDefaultConfig({ auth: {} });
10
+ }
11
+ getAuth(authKey, scopes, clientOptions) {
12
+ const conf = this.getAuthConfig(authKey);
13
+ const base = typeof conf === 'string'
14
+ ? { keyFile: conf }
15
+ : { credentials: conf }; // JWTInput
16
+ const auth = new GoogleAuth({ ...base, scopes, clientOptions });
17
+ return { auth };
18
+ }
19
+ getAuthConfig(authKey) {
20
+ const projectAuth = this.config.auth[authKey];
21
+ if (!projectAuth)
22
+ throw new ImplementationMissingException(`Config of authKey: ${authKey} was not found`);
23
+ return projectAuth;
24
+ }
25
+ async getJWT(authKey, scopes) {
26
+ const authConfig = this.getAuthConfig(authKey);
27
+ if (typeof authConfig === 'string') {
28
+ return new JWT({ keyFile: authConfig, scopes }).authorize();
29
+ }
30
+ throw new NotImplementedYetException('cannot create a JWT from a raw credentials.. need path to file');
31
+ }
32
+ }
33
+ export const ModuleBE_Auth = new ModuleBE_Auth_Class();
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Created by tacb0ss on 07/05/2018.
3
+ */
4
+ import { Module } from '@nu-art/ts-common';
5
+ import { cloudresourcemanager_v1, cloudresourcemanager_v2, serviceusage_v1 } from 'googleapis';
6
+ import { ServiceKey } from './consts.js';
7
+ import Schema$Folder = cloudresourcemanager_v2.Schema$Folder;
8
+ import Schema$Project = cloudresourcemanager_v1.Schema$Project;
9
+ type CreateFolder = {
10
+ parentId: string;
11
+ folderName: string;
12
+ };
13
+ type QueryFolder = {
14
+ parentId: string;
15
+ folderName: string;
16
+ };
17
+ type GoogleCloudManagerConfig = {
18
+ authKey: string;
19
+ scopes: string[];
20
+ };
21
+ export declare class ModuleBE_GcpManager_Class extends Module<GoogleCloudManagerConfig> {
22
+ private cloudServicesManagerAPI;
23
+ private cloudResourcesManagerAPI;
24
+ private cloudResourcesManagerAPIv1;
25
+ constructor();
26
+ protected init(): Promise<void>;
27
+ getOrCreateFolder(parentFolderId: string, folderName: string): Promise<string>;
28
+ createFolder(_request: CreateFolder): Promise<{
29
+ [key: string]: any;
30
+ } | null | undefined>;
31
+ queryFolders(_query: QueryFolder): Promise<cloudresourcemanager_v2.Schema$Folder[]>;
32
+ getFolderId(folder: Schema$Folder): string;
33
+ private _waitForFolderOperation;
34
+ listProjects(filter?: ((project: Schema$Project) => boolean)): Promise<cloudresourcemanager_v1.Schema$Project[]>;
35
+ getOrCreateProjects(parentId: string, ...projects: {
36
+ projectId: string;
37
+ name: string;
38
+ }[]): Promise<(cloudresourcemanager_v1.Schema$Project | {
39
+ [key: string]: any;
40
+ } | undefined)[]>;
41
+ createProject(parentId: string, projectId: string, name?: string): Promise<{
42
+ [key: string]: any;
43
+ } | null | undefined>;
44
+ _waitForProjectOperation(name: string): Promise<{
45
+ [key: string]: any;
46
+ } | null | undefined>;
47
+ getService(serviceKey: ServiceKey, projectId: string): Promise<serviceusage_v1.Schema$GoogleApiServiceusageV1Service>;
48
+ enableService(serviceKey: ServiceKey, enable: boolean, ...projectIds: string[]): Promise<(void | serviceusage_v1.Schema$GoogleApiServiceusageV1Service)[]>;
49
+ private _enableService;
50
+ private _waitForServiceOperation;
51
+ isEnabled(serviceKey: ServiceKey, projectId: string): Promise<boolean>;
52
+ private _isEnabled;
53
+ }
54
+ export declare const ModuleBE_GcpManager: ModuleBE_GcpManager_Class;
55
+ export {};
@@ -0,0 +1,175 @@
1
+ /*
2
+ * Permissions management system, define access level for each of
3
+ * your server apis, and restrict users by giving them access levels
4
+ *
5
+ * Copyright (C) 2020 Adam van der Kruk aka TacB0sS
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ /**
20
+ * Created by tacb0ss on 07/05/2018.
21
+ */
22
+ import { __stringify, BadImplementationException, filterInstances, ImplementationMissingException, Module, ThisShouldNotHappenException, timeout } from '@nu-art/ts-common';
23
+ import { cloudresourcemanager_v1, cloudresourcemanager_v2, serviceusage_v1 } from 'googleapis';
24
+ import { ModuleBE_Auth } from './ModuleBE_Auth.js';
25
+ import { GCPScope } from './consts.js';
26
+ var Serviceusage = serviceusage_v1.Serviceusage;
27
+ var Cloudresourcemanager = cloudresourcemanager_v2.Cloudresourcemanager;
28
+ var CloudresourcemanagerV1 = cloudresourcemanager_v1.Cloudresourcemanager;
29
+ export class ModuleBE_GcpManager_Class extends Module {
30
+ cloudServicesManagerAPI;
31
+ cloudResourcesManagerAPI;
32
+ cloudResourcesManagerAPIv1;
33
+ constructor() {
34
+ super();
35
+ this.setDefaultConfig({ scopes: [GCPScope.CloudPlatform] });
36
+ }
37
+ async init() {
38
+ const auth = ModuleBE_Auth.getAuth(this.config.authKey, this.config.scopes);
39
+ this.cloudServicesManagerAPI = new Serviceusage(auth);
40
+ this.cloudResourcesManagerAPI = new Cloudresourcemanager(auth);
41
+ this.cloudResourcesManagerAPIv1 = new CloudresourcemanagerV1(auth);
42
+ }
43
+ // FOLDERS
44
+ async getOrCreateFolder(parentFolderId, folderName) {
45
+ if (parentFolderId === undefined)
46
+ throw new BadImplementationException('MUST provide a parentFolderId');
47
+ if (folderName === undefined)
48
+ throw new BadImplementationException('MUST provide a folderName');
49
+ const folders = await this.queryFolders({ parentId: parentFolderId, folderName });
50
+ let parentFolder;
51
+ if (folders.length > 1)
52
+ throw new ThisShouldNotHappenException('too many folders for query...');
53
+ else if (folders.length === 1)
54
+ parentFolder = folders[0];
55
+ else
56
+ parentFolder = await this.createFolder({ parentId: parentFolderId, folderName });
57
+ if (!parentFolder)
58
+ throw new BadImplementationException('MUST be parentFolder');
59
+ return this.getFolderId(parentFolder);
60
+ }
61
+ async createFolder(_request) {
62
+ const request = {
63
+ parent: `folders/${_request.parentId}`,
64
+ requestBody: {
65
+ displayName: _request.folderName
66
+ }
67
+ };
68
+ this.logInfo(`Creating GCP folder "${_request.parentId}/${_request.folderName}"`);
69
+ const res = await this.cloudResourcesManagerAPI.folders.create(request);
70
+ return await this._waitForFolderOperation(res.data.name);
71
+ }
72
+ async queryFolders(_query) {
73
+ const query = { query: `parent=folders/${_query.parentId} AND displayName=${_query.folderName}` };
74
+ const res = await this.cloudResourcesManagerAPI.folders.search({ requestBody: query });
75
+ return res.data.folders || [];
76
+ }
77
+ getFolderId(folder) {
78
+ return folder.name.replace('folders/', '');
79
+ }
80
+ async _waitForFolderOperation(name) {
81
+ let retry = 5;
82
+ while (retry >= 0) {
83
+ await timeout(2000);
84
+ const res = await this.cloudResourcesManagerAPI.operations.get({ name });
85
+ if (res.data.done)
86
+ return res.data.response;
87
+ retry--;
88
+ }
89
+ throw new ImplementationMissingException('need better handling here..');
90
+ }
91
+ // PROJECTS
92
+ async listProjects(filter = () => true) {
93
+ const results = await this.cloudResourcesManagerAPIv1.projects.list();
94
+ const projects = results.data.projects || [];
95
+ return projects.filter(filter);
96
+ }
97
+ async getOrCreateProjects(parentId, ...projects) {
98
+ const existingProjects = await this.listProjects(gcproject => !!projects.find(project => project.name === gcproject.name));
99
+ const projectsToCreate = projects
100
+ .filter((project) => !existingProjects.find((gcproject) => gcproject.name === project.name));
101
+ const newProjects = await Promise.all(projectsToCreate.map(project => this.createProject(parentId, project.projectId, project.name)));
102
+ const allProjects = filterInstances([...existingProjects, ...newProjects]);
103
+ return projects.map(project => allProjects.find(gcpProject => gcpProject.name === project.name));
104
+ }
105
+ async createProject(parentId, projectId, name = projectId) {
106
+ const options = {
107
+ projectId,
108
+ name,
109
+ parent: {
110
+ type: 'folder',
111
+ id: parentId
112
+ }
113
+ };
114
+ this.logInfo(`Creating GCP Project "${parentId}/${projectId}/${name}"`);
115
+ const response = await this.cloudResourcesManagerAPIv1.projects.create({ requestBody: options });
116
+ return this._waitForProjectOperation(response.data.name);
117
+ }
118
+ async _waitForProjectOperation(name) {
119
+ let retry = 5;
120
+ while (retry >= 0) {
121
+ await timeout(2000);
122
+ const res = await this.cloudResourcesManagerAPIv1.operations.get({ name });
123
+ if (res.data.done)
124
+ return res.data.response;
125
+ retry--;
126
+ }
127
+ throw new ImplementationMissingException('need better handling here..');
128
+ }
129
+ // SERVICES
130
+ async getService(serviceKey, projectId) {
131
+ const res = await this.cloudServicesManagerAPI.services.get({ name: `projects/${projectId}/services/${serviceKey}` });
132
+ return res.data;
133
+ }
134
+ async enableService(serviceKey, enable, ...projectIds) {
135
+ this.logInfo(`Enabling Service "${serviceKey}" for projects: ${__stringify(projectIds)}`);
136
+ return Promise.all(projectIds.map(projectId => this._enableService(serviceKey, projectId, enable)));
137
+ }
138
+ // @ts-ignore
139
+ async _enableService(serviceKey, projectId, enable = true) {
140
+ let service = await this.getService(serviceKey, projectId);
141
+ if (this._isEnabled(service) === enable)
142
+ return this.logVerbose(`Service "${serviceKey}" was already enabled for project: ${projectId}`);
143
+ const name = service.name;
144
+ let res;
145
+ if (enable)
146
+ res = await this.cloudServicesManagerAPI.services.enable({ name });
147
+ else
148
+ res = await this.cloudServicesManagerAPI.services.disable({ name });
149
+ service = await this._waitForServiceOperation(res.data.name);
150
+ if (this._isEnabled(service))
151
+ this.logVerbose(`Service "${serviceKey}" is now enabled for project: ${projectId}`);
152
+ else
153
+ this.logError(`Service "${serviceKey}" is now disabled for project: ${projectId}`);
154
+ return service;
155
+ }
156
+ async _waitForServiceOperation(name) {
157
+ let retry = 5;
158
+ while (retry >= 0) {
159
+ await timeout(2000);
160
+ const res = await this.cloudServicesManagerAPI.operations.get({ name });
161
+ if (res.data.done)
162
+ return res.data.response?.service;
163
+ retry--;
164
+ }
165
+ throw new ImplementationMissingException('need better handling here..');
166
+ }
167
+ async isEnabled(serviceKey, projectId) {
168
+ const service = await this.getService(serviceKey, projectId);
169
+ return this._isEnabled(service);
170
+ }
171
+ _isEnabled(service) {
172
+ return service.state === 'ENABLED';
173
+ }
174
+ }
175
+ export const ModuleBE_GcpManager = new ModuleBE_GcpManager_Class();
@@ -0,0 +1,21 @@
1
+ import { Module } from '@nu-art/ts-common';
2
+ import { people_v1 } from 'googleapis';
3
+ export declare const standardProperties: string[];
4
+ export declare const standardPhotoProperties: string[];
5
+ export type GoogleContact = people_v1.Schema$Person;
6
+ type Config = {
7
+ authKey?: string;
8
+ };
9
+ export declare class ModuleBE_GoogleContacts_Class extends Module<Config> {
10
+ constructor();
11
+ protected init(): void;
12
+ getContactById: (userEmail: string, contactId: string, authKey?: string) => Promise<people_v1.Schema$Person>;
13
+ delete: (userEmail: string, resource: string, authKey?: string) => Promise<people_v1.Schema$Empty>;
14
+ list: (userEmail: string, pageToken?: string, authKey?: string) => Promise<people_v1.Schema$ListConnectionsResponse>;
15
+ create: (userEmail: string, record: GoogleContact, authKey?: string) => Promise<people_v1.Schema$Person>;
16
+ update: (userEmail: string, record: GoogleContact, authKey?: string) => Promise<people_v1.Schema$Person>;
17
+ updatePhoto: (userEmail: string, person: GoogleContact, contactImageBase64: string, authKey?: string) => Promise<people_v1.Schema$UpdateContactPhotoResponse>;
18
+ private createContactApi;
19
+ }
20
+ export declare const ModuleBE_GoogleContacts: ModuleBE_GoogleContacts_Class;
21
+ export {};
@@ -0,0 +1,97 @@
1
+ /*
2
+ * Permissions management system, define access level for each of
3
+ * your server apis, and restrict users by giving them access levels
4
+ *
5
+ * Copyright (C) 2020 Adam van der Kruk aka TacB0sS
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ import { ImplementationMissingException, Module } from '@nu-art/ts-common';
20
+ import { people_v1 } from 'googleapis';
21
+ import { ModuleBE_Auth } from './ModuleBE_Auth.js';
22
+ export const standardProperties = [
23
+ 'addresses',
24
+ 'birthdays',
25
+ 'emailAddresses',
26
+ 'genders',
27
+ 'names',
28
+ 'phoneNumbers',
29
+ 'userDefined'
30
+ ];
31
+ export const standardPhotoProperties = [
32
+ 'addresses',
33
+ 'birthdays',
34
+ 'emailAddresses',
35
+ 'genders',
36
+ 'names',
37
+ 'phoneNumbers',
38
+ 'userDefined',
39
+ 'photos'
40
+ ];
41
+ export class ModuleBE_GoogleContacts_Class extends Module {
42
+ constructor() {
43
+ super('ModuleBE_GoogleContacts');
44
+ }
45
+ init() {
46
+ }
47
+ getContactById = async (userEmail, contactId, authKey) => {
48
+ const query = {
49
+ resourceName: contactId,
50
+ personFields: `${standardProperties.join(',')},metadata`
51
+ };
52
+ return (await this.createContactApi(userEmail, authKey).people.get(query)).data;
53
+ };
54
+ delete = async (userEmail, resource, authKey) => {
55
+ const contactsApi = await this.createContactApi(userEmail, authKey);
56
+ return (await contactsApi.people.deleteContact({ resourceName: resource })).data;
57
+ };
58
+ list = async (userEmail, pageToken, authKey) => {
59
+ const query = {
60
+ // Only valid resourceName according to https://developers.google.com/people/api/restv1/people.connections/list
61
+ pageToken,
62
+ resourceName: 'people/me',
63
+ pageSize: 1000,
64
+ personFields: standardProperties.join(',')
65
+ };
66
+ return (await this.createContactApi(userEmail, authKey).people.connections.list(query)).data;
67
+ };
68
+ create = async (userEmail, record, authKey) => {
69
+ return (await this.createContactApi(userEmail, authKey).people.createContact({ requestBody: record })).data;
70
+ };
71
+ update = async (userEmail, record, authKey) => {
72
+ const updateRequest = {
73
+ resourceName: record.resourceName,
74
+ updatePersonFields: standardProperties.join(','),
75
+ requestBody: record
76
+ };
77
+ return (await this.createContactApi(userEmail, authKey).people.updateContact(updateRequest)).data;
78
+ };
79
+ updatePhoto = async (userEmail, person, contactImageBase64, authKey) => {
80
+ const updatePhoto = {
81
+ resourceName: person.resourceName,
82
+ requestBody: {
83
+ photoBytes: contactImageBase64,
84
+ personFields: standardPhotoProperties.join(',')
85
+ }
86
+ };
87
+ return (await this.createContactApi(userEmail, authKey).people.updateContactPhoto(updatePhoto)).data;
88
+ };
89
+ createContactApi = (userEmail, authKey) => {
90
+ const finalAuthKey = authKey || this.config.authKey;
91
+ if (!finalAuthKey)
92
+ throw new ImplementationMissingException('missing authkey for google contacts api');
93
+ const auth = ModuleBE_Auth.getAuth(finalAuthKey, ['https://www.googleapis.com/auth/contacts'], { subject: userEmail });
94
+ return new people_v1.People(auth);
95
+ };
96
+ }
97
+ export const ModuleBE_GoogleContacts = new ModuleBE_GoogleContacts_Class();
@@ -0,0 +1,13 @@
1
+ import { Module } from "@nu-art/ts-common";
2
+ import { PublishOptions } from '@google-cloud/pubsub';
3
+ declare class ModuleBE_GooglePubSub_Class extends Module {
4
+ project(projectId: string, authKey?: string): {
5
+ createTopic: (topicName: string) => Promise<import("@google-cloud/pubsub").Topic>;
6
+ topic: (topicName: string, options?: PublishOptions) => {
7
+ publishJson: (json: object) => Promise<string>;
8
+ publish: (buffer: Buffer) => Promise<string>;
9
+ };
10
+ };
11
+ }
12
+ export declare const ModuleBE_GooglePubSub: ModuleBE_GooglePubSub_Class;
13
+ export {};
@@ -0,0 +1,24 @@
1
+ import { Module } from "@nu-art/ts-common";
2
+ import { PubSub } from '@google-cloud/pubsub';
3
+ import { ModuleBE_Auth } from "./ModuleBE_Auth.js";
4
+ class ModuleBE_GooglePubSub_Class extends Module {
5
+ project(projectId, authKey = projectId) {
6
+ const authObject = ModuleBE_Auth.getAuth(authKey, []);
7
+ const auth = authObject.auth;
8
+ const pubSub = new PubSub({ projectId, auth });
9
+ return {
10
+ createTopic: async (topicName) => {
11
+ const [topic] = await pubSub.createTopic(topicName);
12
+ return topic;
13
+ },
14
+ topic: (topicName, options) => {
15
+ const topic = pubSub.topic(topicName, options);
16
+ return {
17
+ publishJson: async (json) => topic.publishJSON(json),
18
+ publish: async (buffer) => topic.publish(buffer)
19
+ };
20
+ }
21
+ };
22
+ }
23
+ }
24
+ export const ModuleBE_GooglePubSub = new ModuleBE_GooglePubSub_Class();
@@ -0,0 +1,31 @@
1
+ import { AnyPrimitive, Module } from '@nu-art/ts-common';
2
+ import { google } from '@google-cloud/secret-manager/build/protos/protos.js';
3
+ import ISecret = google.cloud.secretmanager.v1.ISecret;
4
+ export declare const printCallerIdentity: () => Promise<void>;
5
+ type Secret = {
6
+ key: string;
7
+ projectId: string;
8
+ version?: string;
9
+ };
10
+ export declare class SecretKey<T extends AnyPrimitive> {
11
+ readonly secret: Secret;
12
+ constructor(name: string, parent?: string | undefined);
13
+ get(): Promise<T | undefined>;
14
+ get(fallbackValue: T): Promise<T>;
15
+ set(secret: T): Promise<string>;
16
+ previous(reverseIndex?: number): Promise<T | undefined>;
17
+ modifiedTimestamp(): Promise<number>;
18
+ }
19
+ export declare class ModuleBE_SecretManager_Class extends Module {
20
+ private client;
21
+ getSecretValue: (secret: Secret) => Promise<string | undefined>;
22
+ tryGetSecretValue: (secret: Secret) => Promise<string | undefined>;
23
+ getSecretValueImpl: (secretKey: string) => Promise<string | undefined>;
24
+ updateSecret: (_secret: Secret, data: string) => Promise<string>;
25
+ updateSecretImpl: (secret: ISecret, data: string) => Promise<void>;
26
+ listEnabledVersions: (secret: Secret) => Promise<google.cloud.secretmanager.v1.ISecretVersion[]>;
27
+ getSecretVersionMetadata: (versionName: string) => Promise<google.cloud.secretmanager.v1.ISecretVersion>;
28
+ getOrCreateSecret: (_secret: Secret) => Promise<ISecret>;
29
+ }
30
+ export declare const ModuleBE_SecretManager: ModuleBE_SecretManager_Class;
31
+ export {};
@@ -0,0 +1,129 @@
1
+ import { exists, Logger, Module, MUSTNeverHappenException, ThisShouldNotHappenException } from '@nu-art/ts-common';
2
+ import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
3
+ import { GoogleAuth } from 'google-auth-library';
4
+ export const printCallerIdentity = async () => {
5
+ const logger = new Logger('GCP-Caller');
6
+ const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/cloud-platform' });
7
+ const client = await auth.getClient();
8
+ const projectId = await auth.getProjectId();
9
+ const token = await client.getAccessToken();
10
+ const res = await fetch(`https://oauth2.googleapis.com/tokeninfo?access_token=${token.token}`);
11
+ const info = await res.json();
12
+ logger.logInfo(`🔐 GCP Caller Identity: ${info.email || info.sub}`);
13
+ logger.logInfo(`🏗️ GCP Project ID: ${projectId}`);
14
+ };
15
+ function composeSecretKey(secret) {
16
+ return `projects/${secret.projectId}/secrets/${secret.key}${secret.version ? `/versions/${secret.version}` : ''}`;
17
+ }
18
+ export class SecretKey {
19
+ secret;
20
+ constructor(name, parent = process.env.GCP_PROJECT_ID ?? process.env.GCLOUD_PROJECT) {
21
+ if (!parent)
22
+ throw new MUSTNeverHappenException(`Missing the secret parent project id for secret ${name}`);
23
+ this.secret = Object.freeze({ projectId: parent, key: name });
24
+ }
25
+ async get(fallbackValue, version = 'latest') {
26
+ let rawSecret = await ModuleBE_SecretManager.tryGetSecretValue({ ...this.secret, version });
27
+ if (exists(rawSecret))
28
+ return JSON.parse(rawSecret);
29
+ if (!exists(fallbackValue))
30
+ return undefined;
31
+ const secret = await ModuleBE_SecretManager.getOrCreateSecret(this.secret);
32
+ rawSecret = JSON.stringify(fallbackValue);
33
+ await ModuleBE_SecretManager.updateSecretImpl(secret, rawSecret);
34
+ return fallbackValue;
35
+ }
36
+ async set(secret) {
37
+ return ModuleBE_SecretManager.updateSecret(this.secret, JSON.stringify(secret));
38
+ }
39
+ async previous(reverseIndex = 1) {
40
+ const versions = await ModuleBE_SecretManager.listEnabledVersions(this.secret);
41
+ const version = versions[reverseIndex];
42
+ if (!version?.name)
43
+ throw new MUSTNeverHappenException(`Missing version name at index ${reverseIndex}`);
44
+ const rawSecret = await ModuleBE_SecretManager.getSecretValueImpl(version.name);
45
+ return rawSecret ? JSON.parse(rawSecret) : undefined;
46
+ }
47
+ async modifiedTimestamp() {
48
+ const versions = await ModuleBE_SecretManager.listEnabledVersions(this.secret);
49
+ if (!versions.length)
50
+ return 0;
51
+ const versionName = versions[0].name;
52
+ if (!versionName)
53
+ return 0;
54
+ const metadata = await ModuleBE_SecretManager.getSecretVersionMetadata(versionName);
55
+ return metadata.createTime ? Number(metadata.createTime.seconds) * 1000 : 0;
56
+ }
57
+ }
58
+ export class ModuleBE_SecretManager_Class extends Module {
59
+ client = new SecretManagerServiceClient();
60
+ getSecretValue = async (secret) => {
61
+ return this.getSecretValueImpl(composeSecretKey(secret));
62
+ };
63
+ tryGetSecretValue = async (secret) => {
64
+ try {
65
+ return await this.getSecretValue(secret);
66
+ }
67
+ catch (err) {
68
+ if (err.code === 5)
69
+ return undefined;
70
+ await printCallerIdentity();
71
+ throw new ThisShouldNotHappenException(`Failed to retrieve secret \n Secret: ${JSON.stringify(secret)}`, err);
72
+ }
73
+ };
74
+ getSecretValueImpl = async (secretKey) => {
75
+ const [version] = await this.client.accessSecretVersion({ name: secretKey });
76
+ return version.payload?.data?.toString();
77
+ };
78
+ updateSecret = async (_secret, data) => {
79
+ const secret = await this.getOrCreateSecret(_secret);
80
+ if (!secret.name)
81
+ throw new MUSTNeverHappenException(`Missing secret.name for ${composeSecretKey(_secret)}`);
82
+ await this.updateSecretImpl(secret, data);
83
+ return secret.name;
84
+ };
85
+ updateSecretImpl = async (secret, data) => {
86
+ try {
87
+ await this.client.addSecretVersion({
88
+ parent: secret.name,
89
+ payload: { data: Buffer.from(data, 'utf-8') }
90
+ });
91
+ }
92
+ catch (err) {
93
+ await printCallerIdentity();
94
+ throw new ThisShouldNotHappenException(`Failed to update secret version (${secret})`, err);
95
+ }
96
+ };
97
+ listEnabledVersions = async (secret) => {
98
+ const [versions] = await this.client.listSecretVersions({ parent: composeSecretKey(secret) });
99
+ return versions.filter(v => v.state === 'ENABLED').sort((a, b) => {
100
+ const aVer = Number(a.name?.split('/').pop());
101
+ const bVer = Number(b.name?.split('/').pop());
102
+ return isNaN(bVer - aVer) ? 0 : bVer - aVer;
103
+ });
104
+ };
105
+ getSecretVersionMetadata = async (versionName) => {
106
+ const [version] = await this.client.getSecretVersion({ name: versionName });
107
+ return version;
108
+ };
109
+ getOrCreateSecret = async (_secret) => {
110
+ const name = composeSecretKey(_secret);
111
+ try {
112
+ const [secret] = await this.client.getSecret({ name });
113
+ return secret;
114
+ }
115
+ catch (err) {
116
+ if (err.code !== 5) {
117
+ await printCallerIdentity();
118
+ throw new ThisShouldNotHappenException(`Failed to get secret (${_secret})`, err);
119
+ }
120
+ const [created] = await this.client.createSecret({
121
+ parent: `projects/${_secret.projectId}`,
122
+ secretId: _secret.key,
123
+ secret: { replication: { automatic: {} } }
124
+ });
125
+ return created;
126
+ }
127
+ };
128
+ }
129
+ export const ModuleBE_SecretManager = new ModuleBE_SecretManager_Class();
@@ -0,0 +1,6 @@
1
+ export declare enum ServiceKey {
2
+ DialogFlow = "dialogflow.googleapis.com"
3
+ }
4
+ export declare enum GCPScope {
5
+ CloudPlatform = "https://www.googleapis.com/auth/cloud-platform"
6
+ }
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Permissions management system, define access level for each of
3
+ * your server apis, and restrict users by giving them access levels
4
+ *
5
+ * Copyright (C) 2020 Adam van der Kruk aka TacB0sS
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ export var ServiceKey;
20
+ (function (ServiceKey) {
21
+ ServiceKey["DialogFlow"] = "dialogflow.googleapis.com";
22
+ })(ServiceKey || (ServiceKey = {}));
23
+ export var GCPScope;
24
+ (function (GCPScope) {
25
+ GCPScope["CloudPlatform"] = "https://www.googleapis.com/auth/cloud-platform";
26
+ })(GCPScope || (GCPScope = {}));
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@nu-art/google-services-backend",
3
+ "version": "0.400.5",
4
+ "description": "google-services Backend",
5
+ "keywords": [
6
+ "TacB0sS",
7
+ "create account",
8
+ "express",
9
+ "infra",
10
+ "login",
11
+ "nu-art",
12
+ "permissions",
13
+ "saml",
14
+ "thunderstorm",
15
+ "typescript",
16
+ "user-account"
17
+ ],
18
+ "homepage": "https://github.com/nu-art-js/thunderstorm",
19
+ "bugs": {
20
+ "url": "https://github.com/nu-art-js/thunderstorm/issues"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+ssh://git@github.com:nu-art-js/thunderstorm.git"
25
+ },
26
+ "publishConfig": {
27
+ "directory": "dist",
28
+ "linkDirectory": true
29
+ },
30
+ "license": "Apache-2.0",
31
+ "author": "TacB0sS",
32
+ "scripts": {
33
+ "build": "tsc"
34
+ },
35
+ "dependencies": {
36
+ "@nu-art/ts-common": "0.400.5",
37
+ "@google-cloud/pubsub": "^4.0.0",
38
+ "@google-cloud/secret-manager": "^6.1.0",
39
+ "google-auth-library": "^10.0.0",
40
+ "googleapis": "^152.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@google-cloud/resource-manager": "^6.2.0"
44
+ },
45
+ "unitConfig": {
46
+ "type": "typescript-lib"
47
+ },
48
+ "type": "module",
49
+ "exports": {
50
+ ".": {
51
+ "types": "./index.d.ts",
52
+ "import": "./index.js"
53
+ },
54
+ "./*": {
55
+ "types": "./*.d.ts",
56
+ "import": "./*.js"
57
+ }
58
+ }
59
+ }