-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(server): add support for marketplace integration
- Loading branch information
Showing
11 changed files
with
3,589 additions
and
109 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
...cation-prisma-db/prisma/migrations/20240618142238_add_aws_marketplace_model/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
-- CreateTable | ||
CREATE TABLE "AwsMarketplaceIntegration" ( | ||
"id" TEXT NOT NULL, | ||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"updatedAt" TIMESTAMP(3) NOT NULL, | ||
"email" TEXT NOT NULL, | ||
"productCode" TEXT NOT NULL, | ||
"customerIdentifier" TEXT NOT NULL, | ||
"awsAccountId" TEXT NOT NULL, | ||
"accountId" TEXT, | ||
"workspaceId" TEXT, | ||
|
||
CONSTRAINT "AwsMarketplaceIntegration_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "AwsMarketplaceIntegration_email_key" ON "AwsMarketplaceIntegration"("email"); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "AwsMarketplaceIntegration_customerIdentifier_key" ON "AwsMarketplaceIntegration"("customerIdentifier"); | ||
|
||
-- CreateIndex | ||
CREATE UNIQUE INDEX "AwsMarketplaceIntegration_accountId_key" ON "AwsMarketplaceIntegration"("accountId"); | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "AwsMarketplaceIntegration" ADD CONSTRAINT "AwsMarketplaceIntegration_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "AwsMarketplaceIntegration" ADD CONSTRAINT "AwsMarketplaceIntegration_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
packages/amplication-server/src/core/auth/aws-marketplace/aws-marketplace.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import { Inject, Injectable } from "@nestjs/common"; | ||
import { | ||
MarketplaceMeteringClient, | ||
ResolveCustomerCommand, | ||
ResolveCustomerResult, | ||
} from "@aws-sdk/client-marketplace-metering"; | ||
import { AmplicationLogger } from "@amplication/util/nestjs/logging"; | ||
import { Request, Response } from "express"; | ||
import { Env } from "../../../env"; | ||
import { PrismaService } from "../../../prisma"; | ||
import { ConfigService } from "@nestjs/config"; | ||
import { stringifyUrl } from "query-string"; | ||
import { URL } from "url"; | ||
import { registrationHtmlBody } from "./registration-page"; | ||
import { AWS_MARKETPLACE_INTEGRATION_CALLBACK_PATH } from "./constant"; | ||
import * as cookie from "cookie"; | ||
import { AuthService } from "../auth.service"; | ||
|
||
@Injectable() | ||
export class AwsMarketplaceService { | ||
private awsMeteringClient: MarketplaceMeteringClient; | ||
private host: string; | ||
|
||
private cookieName = "mpid"; | ||
|
||
constructor( | ||
@Inject(AmplicationLogger) private readonly logger: AmplicationLogger, | ||
configService: ConfigService, | ||
private readonly prismaService: PrismaService, | ||
private readonly authService: AuthService | ||
) { | ||
const config = { | ||
credentials: { | ||
accountId: configService.get( | ||
Env.AWS_MARKETPLACE_INTEGRATION_ACCOUNT_ID | ||
), | ||
accessKeyId: configService.get(Env.AWS_MARKETPLACE_INTEGRATION_KEY), | ||
secretAccessKey: configService.get( | ||
Env.AWS_MARKETPLACE_INTEGRATION_SECRET | ||
), | ||
}, | ||
region: "us-east-1", | ||
}; | ||
|
||
this.awsMeteringClient = new MarketplaceMeteringClient(config); | ||
|
||
this.host = configService.get(Env.HOST); | ||
} | ||
|
||
private async resolveCustomer(token: string): Promise<ResolveCustomerResult> { | ||
this.logger.debug(`Resolve aws marketplace customer`, { token }); | ||
|
||
const command = new ResolveCustomerCommand({ | ||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
RegistrationToken: token, | ||
}); | ||
|
||
try { | ||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
const result = await this.awsMeteringClient.send(command); | ||
|
||
this.logger.debug(`Resolve aws marketplace customer response`, { | ||
...result, | ||
}); | ||
return result; | ||
} catch (error) { | ||
this.logger.error(`Failed to resolve aws marketplace customer`, error, { | ||
token, | ||
}); | ||
throw error; | ||
} | ||
} | ||
|
||
async handleAwsMarketplaceRequest( | ||
request: Request, | ||
response: Response | ||
): Promise<string> { | ||
const awsToken = request.body["x-amzn-marketplace-token"]; | ||
|
||
const url = stringifyUrl({ | ||
url: new URL( | ||
AWS_MARKETPLACE_INTEGRATION_CALLBACK_PATH, | ||
this.host | ||
).toString(), | ||
}); | ||
|
||
const clientDomain = new URL(url).hostname; | ||
const cookieDomainParts = clientDomain.split("."); | ||
const cookieDomain = cookieDomainParts | ||
.slice(Math.max(cookieDomainParts.length - 2, 0)) | ||
.join("."); | ||
response.cookie(this.cookieName, awsToken, { | ||
domain: cookieDomain, | ||
secure: true, | ||
httpOnly: true, | ||
}); | ||
|
||
return registrationHtmlBody(url); | ||
} | ||
|
||
async handleAwsMarketplaceRegistration( | ||
request: Request, | ||
response: Response | ||
): Promise<string> { | ||
try { | ||
const contactEmail = request.body["contactEmail"]; | ||
|
||
const reqCookies = cookie.parse(request.headers.cookie); | ||
|
||
const awsToken = reqCookies[this.cookieName]; | ||
const customer = await this.resolveCustomer(awsToken); | ||
|
||
const clientDomain = new URL(this.host).hostname; | ||
const cookieDomainParts = clientDomain.split("."); | ||
const cookieDomain = cookieDomainParts | ||
.slice(Math.max(cookieDomainParts.length - 2, 0)) | ||
.join("."); | ||
|
||
response.cookie(this.cookieName, awsToken, { | ||
domain: cookieDomain, | ||
secure: true, | ||
httpOnly: true, | ||
expires: new Date(), | ||
}); | ||
|
||
const isRegistrationSuccessful = | ||
await this.authService.signupWithBusinessEmail({ | ||
data: { | ||
email: contactEmail, | ||
}, | ||
}); | ||
|
||
if (!isRegistrationSuccessful) { | ||
return `Failed to register. Please contact Amplication support and quote: ${customer.CustomerIdentifier} ${customer.ProductCode}`; | ||
} | ||
|
||
await this.prismaService.awsMarketplaceIntegration.upsert({ | ||
create: { | ||
email: contactEmail, | ||
awsAccountId: customer.CustomerAWSAccountId, | ||
customerIdentifier: customer.CustomerIdentifier, | ||
productCode: customer.ProductCode, | ||
}, | ||
update: { | ||
email: contactEmail, | ||
awsAccountId: customer.CustomerAWSAccountId, | ||
customerIdentifier: customer.CustomerIdentifier, | ||
productCode: customer.ProductCode, | ||
}, | ||
where: { | ||
customerIdentifier: customer.CustomerIdentifier, | ||
}, | ||
}); | ||
|
||
return "Signup successful!<br>Please check your inbox to complete registration and set your password."; | ||
} catch (error) { | ||
this.logger.error( | ||
`Failed to register an AWS Marketplace purchase`, | ||
error | ||
); | ||
return "Failed to register. Please contact Amplication support"; | ||
} | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/amplication-server/src/core/auth/aws-marketplace/constant.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const AWS_MARKETPLACE_INTEGRATION_CALLBACK_PATH = | ||
"/auth/aws-marketplace/callback"; |
Oops, something went wrong.