diff --git a/apps/event-system/services/api/routes/public.ts b/apps/event-system/services/api/routes/public.ts index 1778c23..4860949 100644 --- a/apps/event-system/services/api/routes/public.ts +++ b/apps/event-system/services/api/routes/public.ts @@ -6,7 +6,6 @@ import { checkBlacklist } from '@libs-private/utils/security'; import { get } from 'lodash'; import { IncomingMessage } from 'http'; - export const publicRoute = () => ({ name: 'public', path: '/public', @@ -30,6 +29,7 @@ export const publicRoute = () => ({ 'v1.stripe-webhook.handleWebhook', 'v3.users.mockOauth', + 'v3.users.provider', ], aliases: { @@ -56,7 +56,7 @@ export const publicRoute = () => ({ 'POST v1/embed-tokens/update': 'v1.embed-tokens.public.update', 'POST v1/stripe-webhook': 'v1.stripe-webhook.handleWebhook', 'POST v3/users/mock-oauth': 'v3.users.mockOauth', - + 'GET v3/users/oauth/provider/:provider': 'v3.users.provider', }, cors: { @@ -119,9 +119,15 @@ export const publicRoute = () => ({ json: { strict: false, limit: '5MB', - verify: (req: IncomingMessage, res: any, buf: Buffer, encoding: string | undefined) => { + verify: ( + req: IncomingMessage, + res: any, + buf: Buffer, + encoding: string | undefined + ) => { if (req.url === '/v1/stripe-webhook') { - const validEncoding: BufferEncoding = encoding as BufferEncoding || 'utf8'; + const validEncoding: BufferEncoding = + (encoding as BufferEncoding) || 'utf8'; (req as any).rawBody = buf.toString(validEncoding); } }, @@ -129,7 +135,7 @@ export const publicRoute = () => ({ urlencoded: { extended: true, limit: '1MB', - } + }, }, // Mapping policy setting. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Mapping-policy diff --git a/apps/event-system/services/users/users.service.ts b/apps/event-system/services/users/users.service.ts index 11e9c20..05fa2dd 100644 --- a/apps/event-system/services/users/users.service.ts +++ b/apps/event-system/services/users/users.service.ts @@ -2,6 +2,7 @@ require('dotenv').config(); import { Context, Errors } from 'moleculer'; + const { MoleculerError } = Errors; import axios from 'axios'; @@ -27,34 +28,34 @@ class AuthGenericError extends MoleculerError { } } -class GithubAuthGenericError extends MoleculerError { +class OAuthAccountAlreadyAssociated extends MoleculerError { constructor() { super( - 'Something went wrong during Github authentication', - 500, - 'github-authentication-error', + 'This oauth account is already associated with an existing Buildable account.', + 400, + 'oauth-account-already-associated', {} ); } } -class GitlabAuthGenericError extends MoleculerError { +class GithubAuthGenericError extends MoleculerError { constructor() { super( - 'Something went wrong during Gitlab authentication', + 'Something went wrong during Github authentication', 500, - 'gitlab-authentication-error', + 'github-authentication-error', {} ); } } -class OAuthAccountAlreadyAssociated extends MoleculerError { +class GitlabAuthGenericError extends MoleculerError { constructor() { super( - 'This oauth account is already associated with an existing Buildable account.', - 400, - 'oauth-account-already-associated', + 'Something went wrong during Gitlab authentication', + 500, + 'gitlab-authentication-error', {} ); } @@ -87,7 +88,7 @@ const ACCESS_KEY_PREFIX_LENGTH = 7; const RUST_INTERNAL_API_ENDPOINTS = { CREATE_EVENT_ACCESS_RECORD: `${process.env.CONNECTIONS_API_BASE_URL}v1/public/event-access/default`, - GET_EVENT_ACCESS_RECORD: `${process.env.CONNECTIONS_API_BASE_URL}v1/event-access` + GET_EVENT_ACCESS_RECORD: `${process.env.CONNECTIONS_API_BASE_URL}v1/event-access`, }; const generate_default_event_access_record = ( @@ -165,21 +166,19 @@ module.exports = { await ctx.broker.call( `v${this.version}.${this.name}.remove`, { - id: userId + id: userId, }, { meta: ctx.meta } ); return { - message: 'User deleted successfully' + message: 'User deleted successfully', }; - - } - catch (error) { + } catch (error) { console.error(error); throw new SomethingWentWrong(); } - } + }, }, updateUserFromToken: { params: { @@ -218,7 +217,17 @@ module.exports = { }, }, async handler(ctx: any) { - const { firstName, lastName, avatar, state, role, companyWebsite, companySize, buildingAI, ...rest } = ctx.params; + const { + firstName, + lastName, + avatar, + state, + role, + companyWebsite, + companySize, + buildingAI, + ...rest + } = ctx.params; try { const user = await ctx.broker.call( @@ -251,14 +260,14 @@ module.exports = { ...(state ? { state } : {}), ...(state ? { - stateHistory: [ - { - state, - createdAt: Date.now(), - createdDate: new Date(), - }, - ].concat(get(user, 'stateHistory') || []), - } + stateHistory: [ + { + state, + createdAt: Date.now(), + createdDate: new Date(), + }, + ].concat(get(user, 'stateHistory') || []), + } : {}), }, { meta: ctx.meta } @@ -304,9 +313,13 @@ module.exports = { type: 'string', convert: true, }, + isTerminal: { + type: 'boolean', + optional: true, + }, }, async handler(ctx: any) { - const { type, code } = ctx.params; + const { type, code, isTerminal = false } = ctx.params; const map = { github: async (code: any) => { @@ -439,9 +452,9 @@ module.exports = { const email = Array.isArray(emails) ? get( - emails.find((v) => get(v, 'primary')), - 'email' - ) + emails.find((v) => get(v, 'primary')), + 'email' + ) : null; const username = get(user, 'login'); @@ -591,7 +604,7 @@ module.exports = { lastName, avatar, profileLink, - } = await fn(code); + } = await fn(code, isTerminal); if (!email) { console.debug( @@ -655,6 +668,14 @@ module.exports = { pointers, }); + if (isTerminal) { + const { testKey, liveKey } = await this.createOrGetEventAccessKeys( + token, + pointers + ); + return { testKey, liveKey }; + } + return { token, state, @@ -747,8 +768,25 @@ module.exports = { } }, }, + provider: { + async handler(ctx: Context) { + // @ts-ignore + const { provider }: { provider: string } = ctx.params; + const randomNonce = randomCode(10); + + switch (provider.toLowerCase()) { + case 'github': + // @ts-ignore + ctx.meta.$statusCode = 303; + // @ts-ignore + ctx.meta.$location = `https://github.com/login/oauth/authorize?client_id=${process.env.GITHUB_OAUTH_CLIENT_ID}&state=${randomNonce}&scope=read:user user:email`; + break; + default: + throw new AuthGenericError(); + } + }, + }, mockOauth: { - params: { user: { type: 'object', @@ -776,13 +814,13 @@ module.exports = { visibility: { type: 'string' }, }, }, + }, }, - }, - async handler (ctx: any) { + async handler(ctx: any) { try { - - const secretKey = ctx?.meta?.request?.headers?.['x-mock-user-secret-key']; + const secretKey = + ctx?.meta?.request?.headers?.['x-mock-user-secret-key']; if (!secretKey) { throw new MoleculerError( @@ -833,15 +871,22 @@ module.exports = { } throw new AuthGenericError(); } - + // Create token try { - const { _id, email, userKey, firstName, lastName, state, pointers } = - _user; + const { + _id, + email, + userKey, + firstName, + lastName, + state, + pointers, + } = _user; // use buildableId in client due to old users having user.buildableId == coreId const buildableId = get(_user, 'client.buildableId'); const containerId = get(_user, 'client.containers[0]._id'); - + const token = this.createToken({ _id, email, @@ -853,7 +898,7 @@ module.exports = { lastName, pointers, }); - + return { token, state, @@ -862,15 +907,11 @@ module.exports = { console.error(error); throw new AuthGenericError(); } - - - } - catch (error) { + } catch (error) { console.error(error); throw error; } - } - + }, }, }, @@ -902,28 +943,28 @@ module.exports = { ); const connectedAccounts = connectedAccount ? //ensure old connectedAccount data is updated - (get(_user, 'connectedAccounts') || []).map((i: any) => - i.email === email && i.provider === provider - ? { - ...i, + (get(_user, 'connectedAccounts') || []).map((i: any) => + i.email === email && i.provider === provider + ? { + ...i, + name: `${firstName} ${lastName}`.trim(), + email, + provider, + username, + profileLink, + } + : i + ) + : [ + { name: `${firstName} ${lastName}`.trim(), email, provider, username, profileLink, - } - : i - ) - : [ - { - name: `${firstName} ${lastName}`.trim(), - email, - provider, - username, - profileLink, - connectedAt: Date.now(), - }, - ].concat(get(_user, 'connectedAccounts') || []); + connectedAt: Date.now(), + }, + ].concat(get(_user, 'connectedAccounts') || []); return await ctx.broker.call('v3.users.update', { id: _user._id, @@ -933,8 +974,9 @@ module.exports = { username: _user.username ? _user.username : username, userKey: _user.userKey ? _user.userKey - : `${username || email.substring(0, email.indexOf('@')) - }${randomCode(6)}`, + : `${ + username || email.substring(0, email.indexOf('@')) + }${randomCode(6)}`, [`providers.${provider}`]: user, connectedAccounts, profile: { @@ -1012,8 +1054,9 @@ module.exports = { email, username, accessList: [], - userKey: `${username || email.substring(0, email.indexOf('@')) - }${randomCode(6)}`, + userKey: `${ + username || email.substring(0, email.indexOf('@')) + }${randomCode(6)}`, providers: { [provider]: user, }, @@ -1147,5 +1190,85 @@ module.exports = { return token; }, + async createOrGetEventAccessKeys(token: string, pointers: string[]) { + // Let's list the event access records for the user + const listEventAccessRecords = await axios({ + method: 'get', + url: RUST_INTERNAL_API_ENDPOINTS.GET_EVENT_ACCESS_RECORD, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + 'x-pica-show-all-environments': true, + 'x-pica-secret': `sk_test${pointers[0]}`, + }, + }); + + const filteredEventAccessRecords = + listEventAccessRecords?.data?.rows?.filter( + (record: any) => + !['DEFAULT-LIVE-KEY', 'DEFAULT-TEST-KEY'].includes(record.name) + ); + + // Get existing keys or create new ones + const testKey = await this.getOrCreateAccessKey({ + existingRecords: filteredEventAccessRecords, + environment: 'test', + token, + pointer: pointers[0], + }); + + const liveKey = await this.getOrCreateAccessKey({ + existingRecords: filteredEventAccessRecords, + environment: 'live', + token, + pointer: pointers[1], + }); + + return { testKey, liveKey }; + }, + + async getOrCreateAccessKey({ + existingRecords, + environment, + token, + pointer, + }: { + existingRecords: any[]; + environment: 'test' | 'live'; + token: string; + pointer: string; + }) { + let key = existingRecords?.find( + (record) => record.environment === environment + )?.accessKey; + + if (!key) { + const response = await axios({ + method: 'post', + url: RUST_INTERNAL_API_ENDPOINTS.GET_EVENT_ACCESS_RECORD, + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}`, + 'x-pica-secret': `sk_${environment}${pointer}`, + }, + data: { + name: `${environment}-key`, + group: `${environment}-key`, + connectionType: 'custom', + platform: 'pica', + paths: { + id: null, + event: null, + payload: null, + secret: null, + signature: null, + }, + }, + }); + key = response?.data?.accessKey; + } + + return key; + }, }, }; diff --git a/package-lock.json b/package-lock.json index 8d5992a..05533d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3380,10 +3380,6 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@integrationos/authkit-node": { - "resolved": "packages/connections", - "link": true - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -4237,6 +4233,10 @@ "node": ">= 8" } }, + "node_modules/@picahq/authkit-node": { + "resolved": "packages/connections", + "link": true + }, "node_modules/@seald-io/binary-search-tree": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.3.tgz", @@ -14847,8 +14847,8 @@ } }, "packages/connections": { - "name": "@integrationos/authkit-node", - "version": "1.2.2", + "name": "@picahq/authkit-node", + "version": "1.0.1", "license": "MIT", "dependencies": { "@types/ramda": "^0.28.22",