upd: move stripe webhook into its own service
This commit is contained in:
parent
03039d110a
commit
0627f84e30
|
@ -223,7 +223,9 @@ checkActivityPubGetSignature: false
|
|||
# maxFileSize: 262144000
|
||||
|
||||
# enable stripe identity for ID verification
|
||||
#stripeverify: true
|
||||
# stripeVerify: true
|
||||
# stripeKey: sk_
|
||||
# stripeHookKey: whsec_
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
|
|
@ -298,8 +298,9 @@ checkActivityPubGetSignature: false
|
|||
# maxFileSize: 262144000
|
||||
|
||||
# enable stripe identity for ID verification
|
||||
#stripeverify: true
|
||||
#stripekey: productionkey
|
||||
# stripeVerify: true
|
||||
# stripeKey: sk_
|
||||
# stripeHookKey: whsec_
|
||||
|
||||
# Upload or download file size limits (bytes)
|
||||
#maxFileSize: 262144000
|
||||
|
|
|
@ -313,8 +313,9 @@ checkActivityPubGetSignature: false
|
|||
# maxFileSize: 262144000
|
||||
|
||||
# enable stripe identity for ID verification
|
||||
#stripeverify: true
|
||||
#stripekey: productionkey
|
||||
# stripeVerify: true
|
||||
# stripeKey: sk_
|
||||
# stripeHookKey: whsec_
|
||||
|
||||
# PID File of master process
|
||||
#pidFile: /tmp/misskey.pid
|
||||
|
|
|
@ -108,8 +108,9 @@ type Source = {
|
|||
maxFileSize: number;
|
||||
};
|
||||
|
||||
stripeverify?: boolean;
|
||||
stripekey?: string;
|
||||
stripeVerify?: boolean;
|
||||
stripeKey?: string;
|
||||
stripeHookKey?: string;
|
||||
|
||||
pidFile: string;
|
||||
};
|
||||
|
@ -200,8 +201,9 @@ export type Config = {
|
|||
maxFileSize: number;
|
||||
} | undefined;
|
||||
|
||||
stripeverify: boolean | undefined;
|
||||
stripekey: string;
|
||||
stripeVerify: boolean | undefined;
|
||||
stripeKey: string;
|
||||
stripeHookKey: string;
|
||||
|
||||
pidFile: string;
|
||||
};
|
||||
|
@ -325,8 +327,9 @@ export function loadConfig(): Config {
|
|||
perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
|
||||
deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
|
||||
import: config.import,
|
||||
stripeverify: config.stripeverify ?? false,
|
||||
stripekey: config.stripekey ?? '',
|
||||
stripeVerify: config.stripeVerify ?? false,
|
||||
stripeKey: config.stripeKey ?? '',
|
||||
stripeHookKey: config.stripeHookKey ?? '',
|
||||
pidFile: config.pidFile,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ import { MastodonApiServerService } from './api/mastodon/MastodonApiServerServic
|
|||
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
||||
import { ReversiChannelService } from './api/stream/channels/reversi.js';
|
||||
import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
|
||||
import { StripeHookApiService } from './api/StripeHookApiService.js';
|
||||
import { StripeHookServerService } from './StripeHookServerService.js';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -99,7 +99,7 @@ import { StripeHookApiService } from './api/StripeHookApiService.js';
|
|||
MastodonApiServerService,
|
||||
OAuth2ProviderService,
|
||||
MastoConverters,
|
||||
StripeHookApiService,
|
||||
StripeHookServerService,
|
||||
],
|
||||
exports: [
|
||||
ServerService,
|
||||
|
|
|
@ -32,6 +32,7 @@ import { HealthServerService } from './HealthServerService.js';
|
|||
import { ClientServerService } from './web/ClientServerService.js';
|
||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
||||
import { MastodonApiServerService } from './api/mastodon/MastodonApiServerService.js';
|
||||
import { StripeHookServerService } from './StripeHookServerService.js';
|
||||
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
|
||||
|
||||
const _dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
@ -66,6 +67,7 @@ export class ServerService implements OnApplicationShutdown {
|
|||
private fileServerService: FileServerService,
|
||||
private healthServerService: HealthServerService,
|
||||
private clientServerService: ClientServerService,
|
||||
private stripeHookServerService: StripeHookServerService,
|
||||
private globalEventService: GlobalEventService,
|
||||
private loggerService: LoggerService,
|
||||
private oauth2ProviderService: OAuth2ProviderService,
|
||||
|
@ -109,6 +111,8 @@ export class ServerService implements OnApplicationShutdown {
|
|||
fastify.register(this.mastodonApiServerService.createServer, { prefix: '/api' });
|
||||
fastify.register(this.fileServerService.createServer);
|
||||
fastify.register(this.activityPubServerService.createServer);
|
||||
// only enable stripe webhook if verification is enabled
|
||||
if (this.config.stripeVerify) fastify.register(this.stripeHookServerService.createServer, { prefix: '/stripe' });
|
||||
fastify.register(this.nodeinfoServerService.createServer);
|
||||
fastify.register(this.wellKnownServerService.createServer);
|
||||
fastify.register(this.oauth2ProviderService.createServer, { prefix: '/oauth' });
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: marie and sharkey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type {
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiLocalUser } from '@/models/User.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import cors from '@fastify/cors';
|
||||
import secureJson from 'secure-json-parse';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||
import Stripe from 'stripe';
|
||||
import type Logger from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
|
||||
@Injectable()
|
||||
export class StripeHookServerService {
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private loggerService: LoggerService,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
this.logger = this.loggerService.getLogger('stripe', 'gray');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async stripehook(
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
const stripe = new Stripe(this.config.stripeKey);
|
||||
|
||||
if (request.rawBody == null) {
|
||||
// Bad request
|
||||
reply.code(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = request.rawBody;
|
||||
|
||||
const headers = request.headers;
|
||||
|
||||
function error(status: number, error: { id: string }) {
|
||||
reply.code(status);
|
||||
return { error };
|
||||
}
|
||||
|
||||
let event;
|
||||
|
||||
// Verify the event came from Stripe
|
||||
try {
|
||||
const sig = headers['stripe-signature']!;
|
||||
event = stripe.webhooks.constructEvent(body, sig, this.config.stripeHookKey);
|
||||
} catch (err: any) {
|
||||
// On error, log and return the error message
|
||||
console.log(`❌ Error message: ${err.message}`);
|
||||
reply.code(400)
|
||||
return `Webhook Error: ${err.message}`;
|
||||
}
|
||||
|
||||
// Successfully constructed event
|
||||
switch (event.type) {
|
||||
case 'identity.verification_session.verified': {
|
||||
// All the verification checks passed
|
||||
const verificationSession = event.data.object;
|
||||
|
||||
const user = await this.usersRepository.findOneBy({
|
||||
id: verificationSession.metadata.user_id,
|
||||
host: IsNull(),
|
||||
}) as MiLocalUser;
|
||||
|
||||
if (user == null) {
|
||||
return error(404, {
|
||||
id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
|
||||
});
|
||||
}
|
||||
|
||||
if (user.isSuspended) {
|
||||
return error(403, {
|
||||
id: 'e03a5f46-d309-4865-9b69-56282d94e1eb',
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.succ(`${user.username} has succesfully approved their ID via Session ${user.idSession}`);
|
||||
|
||||
await this.usersRepository.update(user.id, { idCheckRequired: false, idVerified: true });
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reply.code(200)
|
||||
return { received: true };
|
||||
|
||||
// never get here
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
|
||||
const almostDefaultJsonParser: FastifyBodyParser<Buffer> = function (request, rawBody, done) {
|
||||
if (rawBody.length === 0) {
|
||||
const err = new Error('Body cannot be empty!') as any;
|
||||
err.statusCode = 400;
|
||||
return done(err);
|
||||
}
|
||||
|
||||
try {
|
||||
const json = secureJson.parse(rawBody.toString('utf8'), null, {
|
||||
protoAction: 'ignore',
|
||||
constructorAction: 'ignore',
|
||||
});
|
||||
done(null, json);
|
||||
} catch (err: any) {
|
||||
err.statusCode = 400;
|
||||
return done(err);
|
||||
}
|
||||
};
|
||||
|
||||
fastify.register(cors, {
|
||||
origin: '*',
|
||||
});
|
||||
|
||||
fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, almostDefaultJsonParser);
|
||||
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
done();
|
||||
});
|
||||
|
||||
fastify.post<{
|
||||
Body: any,
|
||||
Headers: any,
|
||||
}>('/hook', { config: { rawBody: true }, bodyLimit: 1024 * 64 }, async (request, reply) => await this.stripehook(request, reply));
|
||||
|
||||
done();
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ import endpoints from './endpoints.js';
|
|||
import { ApiCallService } from './ApiCallService.js';
|
||||
import { SignupApiService } from './SignupApiService.js';
|
||||
import { SigninApiService } from './SigninApiService.js';
|
||||
import { StripeHookApiService } from './StripeHookApiService.js';
|
||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
|
||||
|
||||
@Injectable()
|
||||
|
@ -38,7 +37,6 @@ export class ApiServerService {
|
|||
private apiCallService: ApiCallService,
|
||||
private signupApiService: SignupApiService,
|
||||
private signinApiService: SigninApiService,
|
||||
private stripeHookApiService: StripeHookApiService,
|
||||
) {
|
||||
//this.createServer = this.createServer.bind(this);
|
||||
}
|
||||
|
@ -133,13 +131,6 @@ export class ApiServerService {
|
|||
};
|
||||
}>('/signin', (request, reply) => this.signinApiService.signin(request, reply));
|
||||
|
||||
if (this.config.stripeverify) {
|
||||
fastify.post<{
|
||||
Headers: any;
|
||||
Body: any;
|
||||
}>('/stripe/hook', { config: { rawBody: true }, bodyLimit: 1024 * 64 }, (request, reply) => this.stripeHookApiService.stripehook(request, reply));
|
||||
}
|
||||
|
||||
fastify.post<{ Body: { code: string; } }>('/signup-pending', (request, reply) => this.signupApiService.signupPending(request, reply));
|
||||
|
||||
fastify.get('/v1/instance/peers', async (request, reply) => {
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: marie and sharkey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type {
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiLocalUser } from '@/models/User.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
@Injectable()
|
||||
export class StripeHookApiService {
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async stripehook(
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) {
|
||||
const stripe = new Stripe(this.config.stripekey);
|
||||
|
||||
if (!request.rawBody) return reply.code(400).send('error');
|
||||
|
||||
const body: any = request.rawBody
|
||||
|
||||
const headers: any = request.headers;
|
||||
|
||||
function error(status: number, error: { id: string }) {
|
||||
reply.code(status);
|
||||
return { error };
|
||||
}
|
||||
|
||||
let event;
|
||||
console.log(body);
|
||||
// Verify the event came from Stripe
|
||||
try {
|
||||
const sig = headers['stripe-signature'];
|
||||
event = stripe.webhooks.constructEvent(body, sig, 'webhooksecretherewillbereplacedlaterwithconfig');
|
||||
} catch (err: any) {
|
||||
// On error, log and return the error message
|
||||
console.log(`❌ Error message: ${err.message}`);
|
||||
reply.code(400)
|
||||
return `Webhook Error: ${err.message}`;
|
||||
}
|
||||
|
||||
// Successfully constructed event
|
||||
switch (event.type) {
|
||||
case 'identity.verification_session.verified': {
|
||||
// All the verification checks passed
|
||||
const verificationSession = event.data.object;
|
||||
|
||||
const user = await this.usersRepository.findOneBy({
|
||||
id: verificationSession.metadata.user_id,
|
||||
host: IsNull(),
|
||||
}) as MiLocalUser;
|
||||
|
||||
if (user == null) {
|
||||
return error(404, {
|
||||
id: '6cc579cc-885d-43d8-95c2-b8c7fc963280',
|
||||
});
|
||||
}
|
||||
|
||||
if (user.isSuspended) {
|
||||
return error(403, {
|
||||
id: 'e03a5f46-d309-4865-9b69-56282d94e1eb',
|
||||
});
|
||||
}
|
||||
|
||||
await this.usersRepository.update(user.id, { idCheckRequired: false, idVerified: true });
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reply.code(200)
|
||||
return { received: true };
|
||||
|
||||
// never get here
|
||||
}
|
||||
}
|
|
@ -29,6 +29,11 @@ export const meta = {
|
|||
id: 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a',
|
||||
kind: 'permission',
|
||||
},
|
||||
stripeIsDisabled: {
|
||||
message: 'Stripe is disabled.',
|
||||
code: 'STRIPE_IS_DISABLED',
|
||||
id: 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1b',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
@ -48,13 +53,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private config: Config,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if (!this.config.stripeVerify) throw new ApiError(meta.errors.stripeIsDisabled);
|
||||
|
||||
const userProfile = await this.usersRepository.findOne({
|
||||
where: {
|
||||
id: me.id,
|
||||
}
|
||||
});
|
||||
|
||||
const stripe = new Stripe(config.stripekey);
|
||||
const stripe = new Stripe(this.config.stripeKey);
|
||||
|
||||
if (userProfile == null) {
|
||||
throw new ApiError(meta.errors.userIsDeleted);
|
||||
|
|
|
@ -63,6 +63,7 @@ const devConfig: UserConfig = {
|
|||
'/bios': httpUrl,
|
||||
'/cli': httpUrl,
|
||||
'/inbox': httpUrl,
|
||||
'/stripe': httpUrl,
|
||||
'/emoji/': httpUrl,
|
||||
'/notes': {
|
||||
target: httpUrl,
|
||||
|
|
Loading…
Reference in New Issue