@stamhoofd/backend 1.0.0
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/.env.template.json +63 -0
- package/.eslintrc.js +61 -0
- package/README.md +40 -0
- package/index.ts +172 -0
- package/jest.config.js +11 -0
- package/migrations.ts +33 -0
- package/package.json +48 -0
- package/src/crons.ts +845 -0
- package/src/endpoints/admin/organizations/GetOrganizationsCountEndpoint.ts +42 -0
- package/src/endpoints/admin/organizations/GetOrganizationsEndpoint.ts +320 -0
- package/src/endpoints/admin/organizations/PatchOrganizationsEndpoint.ts +171 -0
- package/src/endpoints/auth/CreateAdminEndpoint.ts +137 -0
- package/src/endpoints/auth/CreateTokenEndpoint.test.ts +68 -0
- package/src/endpoints/auth/CreateTokenEndpoint.ts +200 -0
- package/src/endpoints/auth/DeleteTokenEndpoint.ts +31 -0
- package/src/endpoints/auth/ForgotPasswordEndpoint.ts +70 -0
- package/src/endpoints/auth/GetUserEndpoint.test.ts +64 -0
- package/src/endpoints/auth/GetUserEndpoint.ts +57 -0
- package/src/endpoints/auth/PatchApiUserEndpoint.ts +90 -0
- package/src/endpoints/auth/PatchUserEndpoint.ts +122 -0
- package/src/endpoints/auth/PollEmailVerificationEndpoint.ts +37 -0
- package/src/endpoints/auth/RetryEmailVerificationEndpoint.ts +41 -0
- package/src/endpoints/auth/SignupEndpoint.ts +107 -0
- package/src/endpoints/auth/VerifyEmailEndpoint.ts +89 -0
- package/src/endpoints/global/addresses/SearchRegionsEndpoint.ts +95 -0
- package/src/endpoints/global/addresses/ValidateAddressEndpoint.ts +31 -0
- package/src/endpoints/global/caddy/CheckDomainCertEndpoint.ts +101 -0
- package/src/endpoints/global/email/GetEmailAddressEndpoint.ts +53 -0
- package/src/endpoints/global/email/ManageEmailAddressEndpoint.ts +57 -0
- package/src/endpoints/global/files/UploadFile.ts +147 -0
- package/src/endpoints/global/files/UploadImage.ts +119 -0
- package/src/endpoints/global/members/GetMemberFamilyEndpoint.ts +76 -0
- package/src/endpoints/global/members/GetMembersCountEndpoint.ts +43 -0
- package/src/endpoints/global/members/GetMembersEndpoint.ts +429 -0
- package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +734 -0
- package/src/endpoints/global/organizations/CheckRegisterCodeEndpoint.ts +45 -0
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.test.ts +105 -0
- package/src/endpoints/global/organizations/CreateOrganizationEndpoint.ts +146 -0
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.test.ts +52 -0
- package/src/endpoints/global/organizations/GetOrganizationFromDomainEndpoint.ts +80 -0
- package/src/endpoints/global/organizations/GetOrganizationFromUriEndpoint.ts +49 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.test.ts +58 -0
- package/src/endpoints/global/organizations/SearchOrganizationEndpoint.ts +62 -0
- package/src/endpoints/global/payments/ExchangeSTPaymentEndpoint.ts +153 -0
- package/src/endpoints/global/payments/StripeWebhookEndpoint.ts +134 -0
- package/src/endpoints/global/platform/GetPlatformAdminsEndpoint.ts +44 -0
- package/src/endpoints/global/platform/GetPlatformEnpoint.ts +39 -0
- package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +63 -0
- package/src/endpoints/global/registration/GetPaymentRegistrations.ts +68 -0
- package/src/endpoints/global/registration/GetUserBalanceEndpoint.ts +39 -0
- package/src/endpoints/global/registration/GetUserDocumentsEndpoint.ts +80 -0
- package/src/endpoints/global/registration/GetUserMembersEndpoint.ts +41 -0
- package/src/endpoints/global/registration/PatchUserMembersEndpoint.ts +134 -0
- package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +521 -0
- package/src/endpoints/global/registration-periods/GetRegistrationPeriodsEndpoint.ts +37 -0
- package/src/endpoints/global/registration-periods/PatchRegistrationPeriodsEndpoint.ts +115 -0
- package/src/endpoints/global/webshops/GetWebshopFromDomainEndpoint.ts +187 -0
- package/src/endpoints/organization/dashboard/billing/ActivatePackagesEndpoint.ts +424 -0
- package/src/endpoints/organization/dashboard/billing/DeactivatePackageEndpoint.ts +67 -0
- package/src/endpoints/organization/dashboard/billing/GetBillingStatusEndpoint.ts +39 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplateXML.ts +57 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentTemplatesEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/documents/GetDocumentsEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentEndpoint.ts +129 -0
- package/src/endpoints/organization/dashboard/documents/PatchDocumentTemplateEndpoint.ts +114 -0
- package/src/endpoints/organization/dashboard/email/CheckEmailBouncesEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/email/EmailEndpoint.ts +234 -0
- package/src/endpoints/organization/dashboard/email-templates/GetEmailTemplatesEndpoint.ts +62 -0
- package/src/endpoints/organization/dashboard/email-templates/PatchEmailTemplatesEndpoint.ts +85 -0
- package/src/endpoints/organization/dashboard/mollie/CheckMollieEndpoint.ts +80 -0
- package/src/endpoints/organization/dashboard/mollie/ConnectMollieEndpoint.ts +54 -0
- package/src/endpoints/organization/dashboard/mollie/DisconnectMollieEndpoint.ts +49 -0
- package/src/endpoints/organization/dashboard/mollie/GetMollieDashboardEndpoint.ts +63 -0
- package/src/endpoints/organization/dashboard/nolt/CreateNoltTokenEndpoint.ts +61 -0
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.test.ts +64 -0
- package/src/endpoints/organization/dashboard/organization/ApplyRegisterCodeEndpoint.ts +84 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationArchivedGroups.ts +43 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationDeletedGroups.ts +42 -0
- package/src/endpoints/organization/dashboard/organization/GetOrganizationSSOEndpoint.ts +43 -0
- package/src/endpoints/organization/dashboard/organization/GetRegisterCodeEndpoint.ts +65 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.test.ts +281 -0
- package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +338 -0
- package/src/endpoints/organization/dashboard/organization/SetOrganizationDomainEndpoint.ts +196 -0
- package/src/endpoints/organization/dashboard/organization/SetOrganizationSSOEndpoint.ts +50 -0
- package/src/endpoints/organization/dashboard/payments/GetMemberBalanceEndpoint.ts +48 -0
- package/src/endpoints/organization/dashboard/payments/GetPaymentsEndpoint.ts +207 -0
- package/src/endpoints/organization/dashboard/payments/PatchBalanceItemsEndpoint.ts +202 -0
- package/src/endpoints/organization/dashboard/payments/PatchPaymentsEndpoint.ts +233 -0
- package/src/endpoints/organization/dashboard/registration-periods/GetOrganizationRegistrationPeriodsEndpoint.ts +66 -0
- package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +210 -0
- package/src/endpoints/organization/dashboard/stripe/ConnectStripeEndpoint.ts +93 -0
- package/src/endpoints/organization/dashboard/stripe/DeleteStripeAccountEndpoint.ts +59 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountLinkEndpoint.ts +78 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeAccountsEndpoint.ts +40 -0
- package/src/endpoints/organization/dashboard/stripe/GetStripeLoginLinkEndpoint.ts +69 -0
- package/src/endpoints/organization/dashboard/stripe/UpdateStripeAccountEndpoint.ts +52 -0
- package/src/endpoints/organization/dashboard/users/CreateApiUserEndpoint.ts +73 -0
- package/src/endpoints/organization/dashboard/users/DeleteUserEndpoint.ts +60 -0
- package/src/endpoints/organization/dashboard/users/GetApiUsersEndpoint.ts +47 -0
- package/src/endpoints/organization/dashboard/users/GetOrganizationAdminsEndpoint.ts +41 -0
- package/src/endpoints/organization/dashboard/webshops/CreateWebshopEndpoint.ts +217 -0
- package/src/endpoints/organization/dashboard/webshops/DeleteWebshopEndpoint.ts +51 -0
- package/src/endpoints/organization/dashboard/webshops/GetDiscountCodesEndpoint.ts +47 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopOrdersEndpoint.ts +83 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopTicketsEndpoint.ts +68 -0
- package/src/endpoints/organization/dashboard/webshops/GetWebshopUriAvailabilityEndpoint.ts +69 -0
- package/src/endpoints/organization/dashboard/webshops/PatchDiscountCodesEndpoint.ts +125 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopEndpoint.ts +204 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopOrdersEndpoint.ts +278 -0
- package/src/endpoints/organization/dashboard/webshops/PatchWebshopTicketsEndpoint.ts +80 -0
- package/src/endpoints/organization/dashboard/webshops/VerifyWebshopDomainEndpoint.ts +60 -0
- package/src/endpoints/organization/shared/ExchangePaymentEndpoint.ts +379 -0
- package/src/endpoints/organization/shared/GetDocumentHtml.ts +54 -0
- package/src/endpoints/organization/shared/GetPaymentEndpoint.ts +45 -0
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.test.ts +78 -0
- package/src/endpoints/organization/shared/auth/GetOrganizationEndpoint.ts +34 -0
- package/src/endpoints/organization/shared/auth/OpenIDConnectCallbackEndpoint.ts +44 -0
- package/src/endpoints/organization/shared/auth/OpenIDConnectStartEndpoint.ts +82 -0
- package/src/endpoints/organization/webshops/CheckWebshopDiscountCodesEndpoint.ts +59 -0
- package/src/endpoints/organization/webshops/GetOrderByPaymentEndpoint.ts +51 -0
- package/src/endpoints/organization/webshops/GetOrderEndpoint.ts +40 -0
- package/src/endpoints/organization/webshops/GetTicketsEndpoint.ts +124 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.test.ts +130 -0
- package/src/endpoints/organization/webshops/GetWebshopEndpoint.ts +50 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.test.ts +450 -0
- package/src/endpoints/organization/webshops/PlaceOrderEndpoint.ts +335 -0
- package/src/helpers/AddressValidator.test.ts +40 -0
- package/src/helpers/AddressValidator.ts +256 -0
- package/src/helpers/AdminPermissionChecker.ts +1031 -0
- package/src/helpers/AuthenticatedStructures.ts +158 -0
- package/src/helpers/BuckarooHelper.ts +279 -0
- package/src/helpers/CheckSettlements.ts +215 -0
- package/src/helpers/Context.ts +202 -0
- package/src/helpers/CookieHelper.ts +45 -0
- package/src/helpers/ForwardHandler.test.ts +216 -0
- package/src/helpers/ForwardHandler.ts +140 -0
- package/src/helpers/OpenIDConnectHelper.ts +284 -0
- package/src/helpers/StripeHelper.ts +293 -0
- package/src/helpers/StripePayoutChecker.ts +188 -0
- package/src/middleware/ContextMiddleware.ts +16 -0
- package/src/migrations/1646578856-validate-addresses.ts +60 -0
- package/src/seeds/0000000000-example.ts +13 -0
- package/src/seeds/1715028563-user-permissions.ts +52 -0
- package/tests/e2e/stock.test.ts +2120 -0
- package/tests/e2e/tickets.test.ts +926 -0
- package/tests/helpers/StripeMocker.ts +362 -0
- package/tests/helpers/TestServer.ts +21 -0
- package/tests/jest.global.setup.ts +29 -0
- package/tests/jest.setup.ts +59 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Request } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { GroupFactory, OrganizationFactory, Token, UserFactory } from '@stamhoofd/models';
|
|
3
|
+
import { Organization, PermissionLevel, Permissions } from '@stamhoofd/structures';
|
|
4
|
+
|
|
5
|
+
import { testServer } from "../../../../../tests/helpers/TestServer";
|
|
6
|
+
import { GetOrganizationEndpoint } from './GetOrganizationEndpoint';
|
|
7
|
+
|
|
8
|
+
describe("Endpoint.GetOrganization", () => {
|
|
9
|
+
// Test endpoint
|
|
10
|
+
const endpoint = new GetOrganizationEndpoint();
|
|
11
|
+
|
|
12
|
+
test("Get organization as signed in user", async () => {
|
|
13
|
+
const organization = await new OrganizationFactory({}).create()
|
|
14
|
+
const user = await new UserFactory({ organization }).create()
|
|
15
|
+
const groups = await new GroupFactory({ organization }).createMultiple(2)
|
|
16
|
+
const token = await Token.createToken(user)
|
|
17
|
+
|
|
18
|
+
const r = Request.buildJson("GET", "/v3/organization", organization.getApiHost());
|
|
19
|
+
r.headers.authorization = "Bearer " + token.accessToken
|
|
20
|
+
|
|
21
|
+
const response = await testServer.test(endpoint, r);
|
|
22
|
+
expect(response.body).toBeDefined();
|
|
23
|
+
|
|
24
|
+
if (!(response.body instanceof Organization)) {
|
|
25
|
+
throw new Error("Expected Organization")
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
expect(response.body.id).toEqual(organization.id)
|
|
29
|
+
expect(response.body.groups.map(g => g.id).sort()).toEqual(groups.map(g => g.id).sort())
|
|
30
|
+
expect(response.body.privateMeta).toEqual(null)
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("Get organization as admin", async () => {
|
|
34
|
+
const organization = await new OrganizationFactory({}).create()
|
|
35
|
+
const user = await new UserFactory({
|
|
36
|
+
organization,
|
|
37
|
+
permissions: Permissions.create({
|
|
38
|
+
level: PermissionLevel.Read
|
|
39
|
+
})
|
|
40
|
+
}).create()
|
|
41
|
+
|
|
42
|
+
const groups = await new GroupFactory({ organization }).createMultiple(2)
|
|
43
|
+
const token = await Token.createToken(user)
|
|
44
|
+
|
|
45
|
+
const r = Request.buildJson("GET", "/v3/organization", organization.getApiHost());
|
|
46
|
+
r.headers.authorization = "Bearer " + token.accessToken
|
|
47
|
+
|
|
48
|
+
const response = await testServer.test(endpoint, r);
|
|
49
|
+
expect(response.body).toBeDefined();
|
|
50
|
+
|
|
51
|
+
if (!(response.body instanceof Organization)) {
|
|
52
|
+
throw new Error("Expected Organization")
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
expect(response.body.id).toEqual(organization.id)
|
|
56
|
+
expect(response.body.groups.map(g => g.id).sort()).toEqual(groups.map(g => g.id).sort())
|
|
57
|
+
expect(response.body.privateMeta).not.toEqual(null)
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("Get organization as admin of a different organization", async () => {
|
|
61
|
+
const organization = await new OrganizationFactory({}).create()
|
|
62
|
+
const organization2 = await new OrganizationFactory({}).create()
|
|
63
|
+
const user = await new UserFactory({
|
|
64
|
+
organization: organization2,
|
|
65
|
+
permissions: Permissions.create({
|
|
66
|
+
level: PermissionLevel.Read
|
|
67
|
+
})
|
|
68
|
+
}).create()
|
|
69
|
+
|
|
70
|
+
const token = await Token.createToken(user)
|
|
71
|
+
|
|
72
|
+
const r = Request.buildJson("GET", "/v3/organization", organization.getApiHost());
|
|
73
|
+
r.headers.authorization = "Bearer " + token.accessToken
|
|
74
|
+
|
|
75
|
+
await expect(testServer.test(endpoint, r)).rejects.toThrow('The access token is invalid');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { Organization as OrganizationStruct } from "@stamhoofd/structures";
|
|
3
|
+
|
|
4
|
+
import { AuthenticatedStructures } from "../../../../helpers/AuthenticatedStructures";
|
|
5
|
+
import { Context } from "../../../../helpers/Context";
|
|
6
|
+
|
|
7
|
+
type Params = Record<string, never>;
|
|
8
|
+
type Query = undefined;
|
|
9
|
+
type Body = undefined
|
|
10
|
+
type ResponseBody = OrganizationStruct;
|
|
11
|
+
|
|
12
|
+
export class GetOrganizationEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
14
|
+
if (request.method != "GET") {
|
|
15
|
+
return [false];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const params = Endpoint.parseParameters(request.url, "/organization", {});
|
|
19
|
+
|
|
20
|
+
if (params) {
|
|
21
|
+
return [true, params as Params];
|
|
22
|
+
}
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(_: DecodedRequest<Params, Query, Body>) {
|
|
27
|
+
const organization = await Context.setOrganizationScope();
|
|
28
|
+
await Context.optionalAuthenticate({allowWithoutAccount: true})
|
|
29
|
+
|
|
30
|
+
return new Response(
|
|
31
|
+
await AuthenticatedStructures.organization(organization)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { AnyDecoder, Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
|
|
5
|
+
import { Context } from '../../../../helpers/Context';
|
|
6
|
+
import { OpenIDConnectHelper } from '../../../../helpers/OpenIDConnectHelper';
|
|
7
|
+
|
|
8
|
+
type Params = Record<string, never>;
|
|
9
|
+
type Query = undefined;
|
|
10
|
+
type Body = any;
|
|
11
|
+
type ResponseBody = undefined;
|
|
12
|
+
|
|
13
|
+
export class OpenIDConnectCallbackEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
14
|
+
bodyDecoder = AnyDecoder as Decoder<any>;
|
|
15
|
+
|
|
16
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
17
|
+
if (request.method != "POST") {
|
|
18
|
+
return [false];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const params = Endpoint.parseParameters(request.url, "/openid/callback", {});
|
|
22
|
+
|
|
23
|
+
if (params) {
|
|
24
|
+
return [true, params as Params];
|
|
25
|
+
}
|
|
26
|
+
return [false];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
30
|
+
const organization = await Context.setOrganizationScope()
|
|
31
|
+
const configuration = organization.serverMeta.ssoConfiguration
|
|
32
|
+
|
|
33
|
+
if (!configuration) {
|
|
34
|
+
throw new SimpleError({
|
|
35
|
+
code: "invalid_configuration",
|
|
36
|
+
message: "Invalid configuration",
|
|
37
|
+
statusCode: 400
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const helper = new OpenIDConnectHelper(organization, configuration)
|
|
42
|
+
return await helper.callback(request)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Decoder } from '@simonbackx/simple-encoding';
|
|
2
|
+
import { DecodedRequest, Endpoint, Request } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { Webshop } from '@stamhoofd/models';
|
|
5
|
+
import { StartOpenIDFlowStruct } from "@stamhoofd/structures";
|
|
6
|
+
|
|
7
|
+
import { Context } from '../../../../helpers/Context';
|
|
8
|
+
import { OpenIDConnectHelper } from '../../../../helpers/OpenIDConnectHelper';
|
|
9
|
+
|
|
10
|
+
type Params = Record<string, never>;
|
|
11
|
+
type Query = undefined;
|
|
12
|
+
type Body = StartOpenIDFlowStruct;
|
|
13
|
+
type ResponseBody = undefined;
|
|
14
|
+
|
|
15
|
+
export class OpenIDConnectStartEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
16
|
+
bodyDecoder = StartOpenIDFlowStruct as Decoder<StartOpenIDFlowStruct>;
|
|
17
|
+
|
|
18
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
19
|
+
if (request.method != "POST") {
|
|
20
|
+
return [false];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const params = Endpoint.parseParameters(request.url, "/openid/start", {});
|
|
24
|
+
|
|
25
|
+
if (params) {
|
|
26
|
+
return [true, params as Params];
|
|
27
|
+
}
|
|
28
|
+
return [false];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
32
|
+
// Check webshop and/or organization
|
|
33
|
+
const organization = await Context.setOrganizationScope()
|
|
34
|
+
const webshopId = request.body.webshopId;
|
|
35
|
+
let redirectUri = 'https://' + organization.getHost()
|
|
36
|
+
|
|
37
|
+
if (webshopId) {
|
|
38
|
+
const webshop = await Webshop.getByID(webshopId)
|
|
39
|
+
if (!webshop || webshop.organizationId !== organization.id) {
|
|
40
|
+
throw new SimpleError({
|
|
41
|
+
code: "invalid_webshop",
|
|
42
|
+
message: "Invalid webshop",
|
|
43
|
+
statusCode: 400
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
redirectUri = 'https://' + webshop.setRelation(Webshop.organization, organization).getHost()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (request.body.redirectUri) {
|
|
50
|
+
try {
|
|
51
|
+
const allowedHost = new URL(redirectUri);
|
|
52
|
+
const givenUrl = new URL(request.body.redirectUri);
|
|
53
|
+
|
|
54
|
+
if (allowedHost.host === givenUrl.host && givenUrl.protocol === "https:") {
|
|
55
|
+
redirectUri = givenUrl.href
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error('Invalid redirect uri', request.body.redirectUri)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (request.body.spaState.length < 10) {
|
|
63
|
+
throw new SimpleError({
|
|
64
|
+
code: "invalid_state",
|
|
65
|
+
message: "Invalid state",
|
|
66
|
+
statusCode: 400
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const configuration = organization.serverMeta.ssoConfiguration
|
|
71
|
+
if (!configuration) {
|
|
72
|
+
throw new SimpleError({
|
|
73
|
+
code: "invalid_client",
|
|
74
|
+
message: "SSO not configured",
|
|
75
|
+
statusCode: 400
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const helper = new OpenIDConnectHelper(organization, configuration)
|
|
80
|
+
return await helper.startAuthCodeFlow(redirectUri, request.body.provider, request.body.spaState, request.body.prompt)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ArrayDecoder, StringDecoder } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { Webshop, WebshopDiscountCode } from '@stamhoofd/models';
|
|
5
|
+
import { DiscountCode } from "@stamhoofd/structures";
|
|
6
|
+
|
|
7
|
+
import { Context } from "../../../helpers/Context";
|
|
8
|
+
|
|
9
|
+
type Params = { id: string };
|
|
10
|
+
type Query = undefined;
|
|
11
|
+
type Body = string[]
|
|
12
|
+
type ResponseBody = DiscountCode[]
|
|
13
|
+
|
|
14
|
+
export class CheckWebshopDiscountCodesEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
15
|
+
bodyDecoder = new ArrayDecoder(StringDecoder)
|
|
16
|
+
|
|
17
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
18
|
+
if (request.method != "POST") {
|
|
19
|
+
return [false];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const params = Endpoint.parseParameters(request.url, "/webshop/@id/discount-codes", { id: String });
|
|
23
|
+
|
|
24
|
+
if (params) {
|
|
25
|
+
return [true, params as Params];
|
|
26
|
+
}
|
|
27
|
+
return [false];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
31
|
+
const organization = await Context.setOrganizationScope()
|
|
32
|
+
const webshop = await Webshop.getByID(request.params.id)
|
|
33
|
+
if (!webshop || webshop.organizationId != organization.id) {
|
|
34
|
+
throw new SimpleError({
|
|
35
|
+
code: "not_found",
|
|
36
|
+
message: "Webshop not found",
|
|
37
|
+
human: "Deze webshop bestaat niet (meer)"
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (request.body.length > 10) {
|
|
42
|
+
// Auto limit
|
|
43
|
+
request.body = request.body.slice(0, 10)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check all discount codes
|
|
47
|
+
// Return all valid ones
|
|
48
|
+
if (request.body.length > 0) {
|
|
49
|
+
const codes = await WebshopDiscountCode.getActiveCodes(webshop.id, request.body)
|
|
50
|
+
|
|
51
|
+
// todo
|
|
52
|
+
return new Response(
|
|
53
|
+
codes.map(c => c.getStructure())
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return new Response([]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PartialWithoutMethods } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
4
|
+
import { Order } from '@stamhoofd/models';
|
|
5
|
+
import { Payment } from '@stamhoofd/models';
|
|
6
|
+
import { Order as OrderStruct } from "@stamhoofd/structures";
|
|
7
|
+
|
|
8
|
+
import { Context } from "../../../helpers/Context";
|
|
9
|
+
type Params = { id: string; paymentId: string };
|
|
10
|
+
type Query = undefined;
|
|
11
|
+
type Body = undefined
|
|
12
|
+
type ResponseBody = OrderStruct
|
|
13
|
+
|
|
14
|
+
export class GetOrderByPaymentEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
15
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
16
|
+
if (request.method != "GET") {
|
|
17
|
+
return [false];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const params = Endpoint.parseParameters(request.url, "/webshop/@id/payment/@paymentId/order", { id: String, paymentId: String });
|
|
21
|
+
|
|
22
|
+
if (params) {
|
|
23
|
+
return [true, params as Params];
|
|
24
|
+
}
|
|
25
|
+
return [false];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
29
|
+
const organization = await Context.setOrganizationScope()
|
|
30
|
+
const payment = await Payment.getByID(request.params.paymentId)
|
|
31
|
+
|
|
32
|
+
if (!payment || payment.organizationId != organization.id) {
|
|
33
|
+
throw new SimpleError({
|
|
34
|
+
code: "not_found",
|
|
35
|
+
message: "Order not found",
|
|
36
|
+
human: "Deze bestelling bestaat niet (meer)"
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
const [order] = await Order.where({ paymentId: payment.id }, { limit: 1})
|
|
40
|
+
if (!order || order.webshopId != request.params.id || order.organizationId != organization.id) {
|
|
41
|
+
throw new SimpleError({
|
|
42
|
+
code: "not_found",
|
|
43
|
+
message: "Order not found",
|
|
44
|
+
human: "Deze bestelling bestaat niet (meer)"
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
order.setRelation(Order.payment, payment)
|
|
49
|
+
return new Response(await order.getStructure());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { SimpleError } from '@simonbackx/simple-errors';
|
|
3
|
+
import { Order } from '@stamhoofd/models';
|
|
4
|
+
import { Order as OrderStruct } from "@stamhoofd/structures";
|
|
5
|
+
|
|
6
|
+
import { Context } from "../../../helpers/Context";
|
|
7
|
+
type Params = { id: string; orderId: string };
|
|
8
|
+
type Query = undefined;
|
|
9
|
+
type Body = undefined
|
|
10
|
+
type ResponseBody = OrderStruct
|
|
11
|
+
|
|
12
|
+
export class GetOrderEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
13
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
14
|
+
if (request.method != "GET") {
|
|
15
|
+
return [false];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const params = Endpoint.parseParameters(request.url, "/webshop/@id/order/@orderId", { id: String, orderId: String });
|
|
19
|
+
|
|
20
|
+
if (params) {
|
|
21
|
+
return [true, params as Params];
|
|
22
|
+
}
|
|
23
|
+
return [false];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
27
|
+
const organization = await Context.setOrganizationScope()
|
|
28
|
+
const order = await Order.getByID(request.params.orderId)
|
|
29
|
+
|
|
30
|
+
if (!order || order.webshopId != request.params.id || order.organizationId != organization.id) {
|
|
31
|
+
throw new SimpleError({
|
|
32
|
+
code: "not_found",
|
|
33
|
+
message: "Order not found",
|
|
34
|
+
human: "Deze bestelling bestaat niet (meer)"
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return new Response(await order.getStructure());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { AutoEncoder, Decoder, field, StringDecoder } from "@simonbackx/simple-encoding";
|
|
2
|
+
import { DecodedRequest, Endpoint, Request, Response } from "@simonbackx/simple-endpoints";
|
|
3
|
+
import { SimpleError } from "@simonbackx/simple-errors";
|
|
4
|
+
import { Order, Ticket } from '@stamhoofd/models';
|
|
5
|
+
import { TicketOrder, TicketPublic } from "@stamhoofd/structures";
|
|
6
|
+
|
|
7
|
+
import { Context } from "../../../helpers/Context";
|
|
8
|
+
type Params = { id: string };
|
|
9
|
+
|
|
10
|
+
class Query extends AutoEncoder {
|
|
11
|
+
/**
|
|
12
|
+
* Get one ticket by the secret of an individual ticket
|
|
13
|
+
*/
|
|
14
|
+
@field({ decoder: StringDecoder, optional: true })
|
|
15
|
+
secret?: string
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get all tickets of a single order if key is not passed.
|
|
19
|
+
* If key is passed, only return a single ticket, but exclude item information
|
|
20
|
+
*/
|
|
21
|
+
@field({ decoder: StringDecoder, optional: true })
|
|
22
|
+
orderId?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Body = undefined
|
|
26
|
+
type ResponseBody = TicketPublic[] | TicketOrder[]
|
|
27
|
+
|
|
28
|
+
export class GetTicketsEndpoint extends Endpoint<Params, Query, Body, ResponseBody> {
|
|
29
|
+
queryDecoder = Query as Decoder<Query>
|
|
30
|
+
|
|
31
|
+
protected doesMatch(request: Request): [true, Params] | [false] {
|
|
32
|
+
if (request.method != "GET") {
|
|
33
|
+
return [false];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const params = Endpoint.parseParameters(request.url, "/webshop/@id/tickets", { id: String });
|
|
37
|
+
|
|
38
|
+
if (params) {
|
|
39
|
+
return [true, params as Params];
|
|
40
|
+
}
|
|
41
|
+
return [false];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async handle(request: DecodedRequest<Params, Query, Body>) {
|
|
45
|
+
const organization = await Context.setOrganizationScope()
|
|
46
|
+
|
|
47
|
+
if (request.query.secret) {
|
|
48
|
+
const [ticket] = await Ticket.where({
|
|
49
|
+
secret: request.query.secret,
|
|
50
|
+
webshopId: request.params.id,
|
|
51
|
+
organizationId: organization.id
|
|
52
|
+
}, { limit: 1 })
|
|
53
|
+
|
|
54
|
+
if (!ticket || (request.query.orderId && ticket.orderId !== request.query.orderId) || ticket.isDeleted) {
|
|
55
|
+
throw new SimpleError({
|
|
56
|
+
code: "not_found",
|
|
57
|
+
message: "Ticket not found",
|
|
58
|
+
human: "Dit ticket bestaat niet"
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!request.query.orderId) {
|
|
63
|
+
// Include item data
|
|
64
|
+
const order = await Order.getByID(ticket.orderId)
|
|
65
|
+
if (!order || order.webshopId !== request.params.id) {
|
|
66
|
+
console.error("Error: missing order "+ticket.orderId+" for ticket "+ticket.id)
|
|
67
|
+
throw new SimpleError({
|
|
68
|
+
code: "not_found",
|
|
69
|
+
message: "Ticket not found",
|
|
70
|
+
human: "Dit ticket bestaat niet"
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (ticket.itemId) {
|
|
75
|
+
const item = order.data.cart.items.find(i => i.id === ticket.itemId)
|
|
76
|
+
|
|
77
|
+
if (!item) {
|
|
78
|
+
console.error("Error: missing item "+ticket.itemId+" for ticket "+ticket.id)
|
|
79
|
+
throw new SimpleError({
|
|
80
|
+
code: "not_found",
|
|
81
|
+
message: "Ticket not found",
|
|
82
|
+
human: "Dit ticket bestaat niet"
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return new Response([
|
|
87
|
+
TicketPublic.create({
|
|
88
|
+
...ticket,
|
|
89
|
+
items: [item]
|
|
90
|
+
})
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
} else {
|
|
94
|
+
return new Response([
|
|
95
|
+
TicketPublic.create({
|
|
96
|
+
...ticket,
|
|
97
|
+
items: order.data.cart.items
|
|
98
|
+
})
|
|
99
|
+
]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return new Response([
|
|
103
|
+
TicketOrder.create(ticket)
|
|
104
|
+
]);
|
|
105
|
+
} else {
|
|
106
|
+
if (!request.query.orderId) {
|
|
107
|
+
throw new SimpleError({
|
|
108
|
+
code: "not_found",
|
|
109
|
+
message: "At least one query parameter expected: secret, orderId"
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const tickets = await Ticket.where({
|
|
114
|
+
orderId: request.query.orderId,
|
|
115
|
+
webshopId: request.params.id,
|
|
116
|
+
organizationId: organization.id,
|
|
117
|
+
deletedAt: null
|
|
118
|
+
})
|
|
119
|
+
return new Response(
|
|
120
|
+
tickets.map(ticket => TicketOrder.create(ticket))
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Request } from "@simonbackx/simple-endpoints";
|
|
2
|
+
import { OrganizationFactory, Token, UserFactory, WebshopFactory } from '@stamhoofd/models';
|
|
3
|
+
import { PermissionLevel, Permissions, PrivateWebshop, Webshop as WebshopStruct } from '@stamhoofd/structures';
|
|
4
|
+
|
|
5
|
+
import { testServer } from "../../../../tests/helpers/TestServer";
|
|
6
|
+
import { GetWebshopEndpoint } from './GetWebshopEndpoint';
|
|
7
|
+
|
|
8
|
+
describe("Endpoint.GetWebshop", () => {
|
|
9
|
+
// Test endpoint
|
|
10
|
+
const endpoint = new GetWebshopEndpoint();
|
|
11
|
+
|
|
12
|
+
test("Get webshop as signed in user", async () => {
|
|
13
|
+
const organization = await new OrganizationFactory({}).create()
|
|
14
|
+
const user = await new UserFactory({ organization }).create()
|
|
15
|
+
const token = await Token.createToken(user)
|
|
16
|
+
const webshop = await new WebshopFactory({ organizationId: organization.id }).create()
|
|
17
|
+
|
|
18
|
+
const r = Request.buildJson("GET", "/v244/webshop/" + webshop.id, organization.getApiHost());
|
|
19
|
+
r.headers.authorization = "Bearer " + token.accessToken
|
|
20
|
+
|
|
21
|
+
const response = await testServer.test(endpoint, r);
|
|
22
|
+
expect(response.body).toBeDefined();
|
|
23
|
+
|
|
24
|
+
expect(response.body.id).toEqual(webshop.id)
|
|
25
|
+
expect((response.body as any).privateMeta).toBeUndefined()
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("Allow access without organization scope in old v243", async () => {
|
|
29
|
+
const organization = await new OrganizationFactory({}).create()
|
|
30
|
+
const webshop = await new WebshopFactory({ organizationId: organization.id }).create()
|
|
31
|
+
|
|
32
|
+
const r = Request.buildJson("GET", "/v243/webshop/" + webshop.id);
|
|
33
|
+
|
|
34
|
+
const response = await testServer.test(endpoint, r);
|
|
35
|
+
expect(response.body).toBeDefined();
|
|
36
|
+
|
|
37
|
+
expect(response.body.id).toEqual(webshop.id)
|
|
38
|
+
expect((response.body as any).privateMeta).toBeUndefined()
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("Do not allow access without organization scope in v244", async () => {
|
|
42
|
+
const organization = await new OrganizationFactory({}).create()
|
|
43
|
+
const webshop = await new WebshopFactory({ organizationId: organization.id }).create()
|
|
44
|
+
const r = Request.buildJson("GET", "/v244/webshop/" + webshop.id);
|
|
45
|
+
await expect(testServer.test(endpoint, r)).rejects.toThrow('Please specify the organization in the hostname');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("Get webshop as admin", async () => {
|
|
49
|
+
const organization = await new OrganizationFactory({}).create()
|
|
50
|
+
const user = await new UserFactory({
|
|
51
|
+
organization,
|
|
52
|
+
permissions: Permissions.create({
|
|
53
|
+
level: PermissionLevel.Read
|
|
54
|
+
})
|
|
55
|
+
}).create()
|
|
56
|
+
const token = await Token.createToken(user)
|
|
57
|
+
|
|
58
|
+
const webshop = await new WebshopFactory({ organizationId: organization.id }).create()
|
|
59
|
+
|
|
60
|
+
const r = Request.buildJson("GET", "/v244/webshop/" + webshop.id, organization.getApiHost());
|
|
61
|
+
r.headers.authorization = "Bearer " + token.accessToken
|
|
62
|
+
|
|
63
|
+
const response = await testServer.test(endpoint, r);
|
|
64
|
+
expect(response.body).toBeDefined();
|
|
65
|
+
|
|
66
|
+
expect(response.body.id).toEqual(webshop.id)
|
|
67
|
+
expect((response.body as any).privateMeta).toBeDefined()
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("Get webshop as admin that does not have access to specific webshop", async () => {
|
|
71
|
+
const organization = await new OrganizationFactory({}).create()
|
|
72
|
+
const user = await new UserFactory({
|
|
73
|
+
organization,
|
|
74
|
+
permissions: Permissions.create({
|
|
75
|
+
level: PermissionLevel.None
|
|
76
|
+
})
|
|
77
|
+
}).create()
|
|
78
|
+
const token = await Token.createToken(user)
|
|
79
|
+
|
|
80
|
+
const webshop = await new WebshopFactory({ organizationId: organization.id }).create()
|
|
81
|
+
|
|
82
|
+
const r = Request.buildJson("GET", "/v244/webshop/" + webshop.id, organization.getApiHost());
|
|
83
|
+
r.headers.authorization = "Bearer " + token.accessToken
|
|
84
|
+
|
|
85
|
+
const response = await testServer.test(endpoint, r);
|
|
86
|
+
expect(response.body).toBeDefined();
|
|
87
|
+
|
|
88
|
+
expect(response.body.id).toEqual(webshop.id)
|
|
89
|
+
expect((response.body as any).privateMeta).toBeUndefined()
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("Get webshop as admin of a different organization", async () => {
|
|
93
|
+
const organization = await new OrganizationFactory({}).create()
|
|
94
|
+
const organization2 = await new OrganizationFactory({}).create()
|
|
95
|
+
const user = await new UserFactory({
|
|
96
|
+
organization: organization2,
|
|
97
|
+
permissions: Permissions.create({
|
|
98
|
+
level: PermissionLevel.Read
|
|
99
|
+
})
|
|
100
|
+
}).create()
|
|
101
|
+
|
|
102
|
+
const token = await Token.createToken(user)
|
|
103
|
+
const webshop = await new WebshopFactory({ organizationId: organization.id }).create()
|
|
104
|
+
|
|
105
|
+
const r = Request.buildJson("GET", "/v244/webshop/" + webshop.id, organization.getApiHost());
|
|
106
|
+
r.headers.authorization = "Bearer " + token.accessToken
|
|
107
|
+
|
|
108
|
+
await expect(testServer.test(endpoint, r)).rejects.toThrow('The access token is invalid');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("If organization scope is missing in v243, access is still checked correctly", async () => {
|
|
112
|
+
const organization = await new OrganizationFactory({}).create()
|
|
113
|
+
const organization2 = await new OrganizationFactory({}).create()
|
|
114
|
+
const user = await new UserFactory({
|
|
115
|
+
organization: organization2,
|
|
116
|
+
permissions: Permissions.create({
|
|
117
|
+
level: PermissionLevel.Full
|
|
118
|
+
})
|
|
119
|
+
}).create()
|
|
120
|
+
|
|
121
|
+
const token = await Token.createToken(user)
|
|
122
|
+
const webshop = await new WebshopFactory({ organizationId: organization.id }).create()
|
|
123
|
+
|
|
124
|
+
const r = Request.buildJson("GET", "/v243/webshop/" + webshop.id);
|
|
125
|
+
r.headers.authorization = "Bearer " + token.accessToken
|
|
126
|
+
|
|
127
|
+
await expect(testServer.test(endpoint, r)).rejects.toThrow('The access token is invalid');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
});
|