@things-factory/auth-base 10.1.215 → 10.1.218
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/config/config.development.js +2 -4
- package/config/config.production.js +1 -2
- package/dist-client/actions/auth.d.ts +1 -0
- package/dist-client/actions/auth.js +6 -0
- package/dist-client/actions/auth.js.map +1 -1
- package/dist-client/reducers/auth.d.ts +8 -0
- package/dist-client/reducers/auth.js +8 -2
- package/dist-client/reducers/auth.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/constants/error-code.d.ts +1 -0
- package/dist-server/constants/error-code.js +2 -1
- package/dist-server/constants/error-code.js.map +1 -1
- package/dist-server/controllers/auth.d.ts +0 -1
- package/dist-server/controllers/auth.js +0 -1
- package/dist-server/controllers/auth.js.map +1 -1
- package/dist-server/controllers/password-setup.d.ts +33 -0
- package/dist-server/controllers/password-setup.js +77 -0
- package/dist-server/controllers/password-setup.js.map +1 -0
- package/dist-server/controllers/reset-password.d.ts +8 -1
- package/dist-server/controllers/reset-password.js +21 -11
- package/dist-server/controllers/reset-password.js.map +1 -1
- package/dist-server/controllers/signin.js +10 -0
- package/dist-server/controllers/signin.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +2 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/middlewares/jwt-authenticate-middleware.js +8 -25
- package/dist-server/middlewares/jwt-authenticate-middleware.js.map +1 -1
- package/dist-server/router/auth-public-process-router.js +3 -41
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/service/access-request/access-request.js +2 -2
- package/dist-server/service/access-request/access-request.js.map +1 -1
- package/dist-server/service/audit/audit-kinds.js +1 -0
- package/dist-server/service/audit/audit-kinds.js.map +1 -1
- package/dist-server/service/audit/audit-subscriber.d.ts +1 -7
- package/dist-server/service/audit/audit-subscriber.js +2 -33
- package/dist-server/service/audit/audit-subscriber.js.map +1 -1
- package/dist-server/service/audit/index.d.ts +1 -1
- package/dist-server/service/index.d.ts +2 -4
- package/dist-server/service/index.js +22 -27
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/open-fields.js +0 -2
- package/dist-server/service/open-fields.js.map +1 -1
- package/dist-server/service/user/user-mutation.d.ts +27 -6
- package/dist-server/service/user/user-mutation.js +26 -114
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/service/user/user-query.d.ts +3 -4
- package/dist-server/service/user/user-query.js +7 -32
- package/dist-server/service/user/user-query.js.map +1 -1
- package/dist-server/service/user/user-types.d.ts +6 -2
- package/dist-server/service/user/user-types.js +20 -9
- package/dist-server/service/user/user-types.js.map +1 -1
- package/dist-server/service/user/user.js +17 -8
- package/dist-server/service/user/user.js.map +1 -1
- package/dist-server/service/verification-token/verification-token.d.ts +1 -0
- package/dist-server/service/verification-token/verification-token.js +1 -0
- package/dist-server/service/verification-token/verification-token.js.map +1 -1
- package/dist-server/templates/reset-password-email.d.ts +10 -3
- package/dist-server/templates/reset-password-email.js +8 -4
- package/dist-server/templates/reset-password-email.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/verification-token-rule.js +3 -1
- package/dist-server/utils/verification-token-rule.js.map +1 -1
- package/package.json +2 -2
- package/tests/add-user-gates.test.ts +34 -0
- package/tests/list-inheritance-db.test.ts +0 -15
- package/tests/lock-recovery.test.ts +2 -2
- package/tests/open-doors.test.ts +2 -5
- package/tests/password-reset-rule-db.test.ts +17 -16
- package/tests/password-setup-db.test.ts +213 -0
- package/tests/picture-reducer.test.ts +25 -0
- package/tests/verification-token-db.test.ts +12 -7
- package/translations/en.json +2 -2
- package/translations/es.json +2 -2
- package/translations/ja.json +2 -2
- package/translations/ko.json +2 -2
- package/translations/ms.json +2 -2
- package/translations/zh.json +2 -2
- package/dist-server/controllers/invitation.d.ts +0 -75
- package/dist-server/controllers/invitation.js +0 -195
- package/dist-server/controllers/invitation.js.map +0 -1
- package/dist-server/controllers/utils/make-invitation-token.d.ts +0 -1
- package/dist-server/controllers/utils/make-invitation-token.js +0 -9
- package/dist-server/controllers/utils/make-invitation-token.js.map +0 -1
- package/dist-server/controllers/utils/save-invitation-token.d.ts +0 -5
- package/dist-server/controllers/utils/save-invitation-token.js +0 -12
- package/dist-server/controllers/utils/save-invitation-token.js.map +0 -1
- package/dist-server/service/invitation/index.d.ts +0 -5
- package/dist-server/service/invitation/index.js +0 -9
- package/dist-server/service/invitation/index.js.map +0 -1
- package/dist-server/service/invitation/invitation-mutation.d.ts +0 -20
- package/dist-server/service/invitation/invitation-mutation.js +0 -109
- package/dist-server/service/invitation/invitation-mutation.js.map +0 -1
- package/dist-server/service/invitation/invitation-query.d.ts +0 -23
- package/dist-server/service/invitation/invitation-query.js +0 -89
- package/dist-server/service/invitation/invitation-query.js.map +0 -1
- package/dist-server/service/invitation/invitation-types.d.ts +0 -5
- package/dist-server/service/invitation/invitation-types.js +0 -21
- package/dist-server/service/invitation/invitation-types.js.map +0 -1
- package/dist-server/service/invitation/invitation.d.ts +0 -26
- package/dist-server/service/invitation/invitation.js +0 -142
- package/dist-server/service/invitation/invitation.js.map +0 -1
- package/dist-server/templates/invitation-email.d.ts +0 -6
- package/dist-server/templates/invitation-email.js +0 -82
- package/dist-server/templates/invitation-email.js.map +0 -1
- package/dist-server/utils/invitation-state.d.ts +0 -52
- package/dist-server/utils/invitation-state.js +0 -76
- package/dist-server/utils/invitation-state.js.map +0 -1
- package/tests/invitation-db.test.ts +0 -416
- package/tests/invitation-route.test.ts +0 -116
- package/tests/invitation-state.test.ts +0 -92
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InvitationQuery = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const type_graphql_1 = require("type-graphql");
|
|
6
|
-
const shell_1 = require("@things-factory/shell");
|
|
7
|
-
const invitation_state_js_1 = require("../../utils/invitation-state.js");
|
|
8
|
-
const invitation_js_1 = require("./invitation.js");
|
|
9
|
-
const invitation_types_js_1 = require("./invitation-types.js");
|
|
10
|
-
/*
|
|
11
|
-
* ── What changed here, and why ──────────────────────────────────────────────
|
|
12
|
-
* These read by `{ email, reference, type }` and took no domain at all — `invitations` did not
|
|
13
|
-
* even take a context, so there was nothing to compare a caller against. An id from one tenant
|
|
14
|
-
* and an id from another were the same kind of argument.
|
|
15
|
-
*
|
|
16
|
-
* They are the invitation **list** now: this domain's invitations, scoped the way every other
|
|
17
|
-
* list in this package is scoped. `reference` is not read; the domain is a column.
|
|
18
|
-
*/
|
|
19
|
-
const GATE = '@privilege(category: "user", privilege: "query", domainOwnerGranted: true, superUserGranted: true)';
|
|
20
|
-
let InvitationQuery = class InvitationQuery {
|
|
21
|
-
async invitation(id, context) {
|
|
22
|
-
const { domain } = context.state;
|
|
23
|
-
return await (0, shell_1.getRepository)(invitation_js_1.Invitation).findOne({
|
|
24
|
-
where: { id, domain: { id: domain.id } },
|
|
25
|
-
relations: ['domain', 'roles']
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Who was invited to this domain, by whom, when, and whether they came in.
|
|
30
|
-
*
|
|
31
|
-
* ⚠ This is why the rows are kept. Accepting used to delete the row, so the moment somebody
|
|
32
|
-
* joined, the fact that they had been invited stopped existing — there was nothing to put on a
|
|
33
|
-
* screen, so no screen was built, so nothing called any of this.
|
|
34
|
-
*/
|
|
35
|
-
async invitations(params, context) {
|
|
36
|
-
const { domain } = context.state;
|
|
37
|
-
const queryBuilder = (0, shell_1.getQueryBuilderFromListParams)({
|
|
38
|
-
domain,
|
|
39
|
-
// this domain's rows only - a parent domain's are another tenant's, whatever `inherited` the caller sent
|
|
40
|
-
params: { ...params, inherited: shell_1.InheritedValueType.None },
|
|
41
|
-
repository: (0, shell_1.getRepository)(invitation_js_1.Invitation),
|
|
42
|
-
alias: 'invitation',
|
|
43
|
-
searchables: ['email']
|
|
44
|
-
});
|
|
45
|
-
const [items, total] = await queryBuilder.getManyAndCount();
|
|
46
|
-
return { items, total };
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* What state this invitation is in, read against the clock.
|
|
50
|
-
*
|
|
51
|
-
* ⚠ Not the stored column on its own. A pending row whose expiry has passed **is** expired
|
|
52
|
-
* whether or not anything has been round to write that down — otherwise the list would show a
|
|
53
|
-
* live invitation for a token that no longer opens anything, which is the more dangerous of
|
|
54
|
-
* the two ways to be wrong here.
|
|
55
|
-
*/
|
|
56
|
-
async state(invitation) {
|
|
57
|
-
return (0, invitation_state_js_1.invitationState)(invitation);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
exports.InvitationQuery = InvitationQuery;
|
|
61
|
-
tslib_1.__decorate([
|
|
62
|
-
(0, type_graphql_1.Directive)(GATE),
|
|
63
|
-
(0, type_graphql_1.Query)(returns => invitation_js_1.Invitation, { nullable: true, description: 'To fetch an invitation of this domain' }),
|
|
64
|
-
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
65
|
-
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
66
|
-
tslib_1.__metadata("design:type", Function),
|
|
67
|
-
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
68
|
-
tslib_1.__metadata("design:returntype", Promise)
|
|
69
|
-
], InvitationQuery.prototype, "invitation", null);
|
|
70
|
-
tslib_1.__decorate([
|
|
71
|
-
(0, type_graphql_1.Directive)(GATE),
|
|
72
|
-
(0, type_graphql_1.Query)(returns => invitation_types_js_1.InvitationList, { description: "To fetch this domain's invitations" }),
|
|
73
|
-
tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
|
|
74
|
-
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
75
|
-
tslib_1.__metadata("design:type", Function),
|
|
76
|
-
tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
|
|
77
|
-
tslib_1.__metadata("design:returntype", Promise)
|
|
78
|
-
], InvitationQuery.prototype, "invitations", null);
|
|
79
|
-
tslib_1.__decorate([
|
|
80
|
-
(0, type_graphql_1.FieldResolver)(type => String),
|
|
81
|
-
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
82
|
-
tslib_1.__metadata("design:type", Function),
|
|
83
|
-
tslib_1.__metadata("design:paramtypes", [invitation_js_1.Invitation]),
|
|
84
|
-
tslib_1.__metadata("design:returntype", Promise)
|
|
85
|
-
], InvitationQuery.prototype, "state", null);
|
|
86
|
-
exports.InvitationQuery = InvitationQuery = tslib_1.__decorate([
|
|
87
|
-
(0, type_graphql_1.Resolver)(invitation_js_1.Invitation)
|
|
88
|
-
], InvitationQuery);
|
|
89
|
-
//# sourceMappingURL=invitation-query.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invitation-query.js","sourceRoot":"","sources":["../../../server/service/invitation/invitation-query.ts"],"names":[],"mappings":";;;;AAAA,+CAA8F;AAE9F,iDAAmH;AAEnH,yEAAiE;AACjE,mDAA4C;AAC5C,+DAAsD;AAEtD;;;;;;;;GAQG;AACH,MAAM,IAAI,GAAG,oGAAoG,CAAA;AAG1G,IAAM,eAAe,GAArB,MAAM,eAAe;IAGpB,AAAN,KAAK,CAAC,UAAU,CAAY,EAAU,EAAS,OAAwB;QACrE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,OAAO,MAAM,IAAA,qBAAa,EAAC,0BAAU,CAAC,CAAC,OAAO,CAAC;YAC7C,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE;YACxC,SAAS,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;SAC/B,CAAC,CAAA;IACJ,CAAC;IAED;;;;;;OAMG;IAGG,AAAN,KAAK,CAAC,WAAW,CACU,MAAiB,EACnC,OAAwB;QAE/B,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAA;QAEhC,MAAM,YAAY,GAAG,IAAA,qCAA6B,EAAC;YACjD,MAAM;YACN,yGAAyG;YACzG,MAAM,EAAE,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,0BAAkB,CAAC,IAAI,EAAE;YACzD,UAAU,EAAE,IAAA,qBAAa,EAAC,0BAAU,CAAC;YACrC,KAAK,EAAE,YAAY;YACnB,WAAW,EAAE,CAAC,OAAO,CAAC;SACvB,CAAC,CAAA;QAEF,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,YAAY,CAAC,eAAe,EAAE,CAAA;QAE3D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACzB,CAAC;IAED;;;;;;;OAOG;IAEG,AAAN,KAAK,CAAC,KAAK,CAAS,UAAsB;QACxC,OAAO,IAAA,qCAAe,EAAC,UAAU,CAAC,CAAA;IACpC,CAAC;CACF,CAAA;AArDY,0CAAe;AAGpB;IAFL,IAAA,wBAAS,EAAC,IAAI,CAAC;IACf,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,0BAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,uCAAuC,EAAE,CAAC;IACrF,mBAAA,IAAA,kBAAG,EAAC,IAAI,CAAC,CAAA;IAAc,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;iDAO7C;AAWK;IAFL,IAAA,wBAAS,EAAC,IAAI,CAAC;IACf,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oCAAc,EAAE,EAAE,WAAW,EAAE,oCAAoC,EAAE,CAAC;IAErF,mBAAA,IAAA,mBAAI,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAS,CAAC,CAAA;IACvB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;6CAD2B,iBAAS;;kDAiB3C;AAWK;IADL,IAAA,4BAAa,EAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC;IACjB,mBAAA,IAAA,mBAAI,GAAE,CAAA;;6CAAa,0BAAU;;4CAEzC;0BApDU,eAAe;IAD3B,IAAA,uBAAQ,EAAC,0BAAU,CAAC;GACR,eAAe,CAqD3B","sourcesContent":["import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'\n\nimport { getQueryBuilderFromListParams, InheritedValueType, getRepository, ListParam } from '@things-factory/shell'\n\nimport { invitationState } from '../../utils/invitation-state.js'\nimport { Invitation } from './invitation.js'\nimport { InvitationList } from './invitation-types.js'\n\n/*\n * ── What changed here, and why ──────────────────────────────────────────────\n * These read by `{ email, reference, type }` and took no domain at all — `invitations` did not\n * even take a context, so there was nothing to compare a caller against. An id from one tenant\n * and an id from another were the same kind of argument.\n *\n * They are the invitation **list** now: this domain's invitations, scoped the way every other\n * list in this package is scoped. `reference` is not read; the domain is a column.\n */\nconst GATE = '@privilege(category: \"user\", privilege: \"query\", domainOwnerGranted: true, superUserGranted: true)'\n\n@Resolver(Invitation)\nexport class InvitationQuery {\n @Directive(GATE)\n @Query(returns => Invitation, { nullable: true, description: 'To fetch an invitation of this domain' })\n async invitation(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Invitation> {\n const { domain } = context.state\n\n return await getRepository(Invitation).findOne({\n where: { id, domain: { id: domain.id } },\n relations: ['domain', 'roles']\n })\n }\n\n /**\n * Who was invited to this domain, by whom, when, and whether they came in.\n *\n * ⚠ This is why the rows are kept. Accepting used to delete the row, so the moment somebody\n * joined, the fact that they had been invited stopped existing — there was nothing to put on a\n * screen, so no screen was built, so nothing called any of this.\n */\n @Directive(GATE)\n @Query(returns => InvitationList, { description: \"To fetch this domain's invitations\" })\n async invitations(\n @Args(type => ListParam) params: ListParam,\n @Ctx() context: ResolverContext\n ): Promise<InvitationList> {\n const { domain } = context.state\n\n const queryBuilder = getQueryBuilderFromListParams({\n domain,\n // this domain's rows only - a parent domain's are another tenant's, whatever `inherited` the caller sent\n params: { ...params, inherited: InheritedValueType.None },\n repository: getRepository(Invitation),\n alias: 'invitation',\n searchables: ['email']\n })\n\n const [items, total] = await queryBuilder.getManyAndCount()\n\n return { items, total }\n }\n\n /**\n * What state this invitation is in, read against the clock.\n *\n * ⚠ Not the stored column on its own. A pending row whose expiry has passed **is** expired\n * whether or not anything has been round to write that down — otherwise the list would show a\n * live invitation for a token that no longer opens anything, which is the more dangerous of\n * the two ways to be wrong here.\n */\n @FieldResolver(type => String)\n async state(@Root() invitation: Invitation): Promise<string> {\n return invitationState(invitation)\n }\n}\n"]}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.InvitationList = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const invitation_js_1 = require("./invitation.js");
|
|
6
|
-
const type_graphql_1 = require("type-graphql");
|
|
7
|
-
let InvitationList = class InvitationList {
|
|
8
|
-
};
|
|
9
|
-
exports.InvitationList = InvitationList;
|
|
10
|
-
tslib_1.__decorate([
|
|
11
|
-
(0, type_graphql_1.Field)(type => [invitation_js_1.Invitation], { nullable: true, description: 'The list of invitation items.' }),
|
|
12
|
-
tslib_1.__metadata("design:type", Array)
|
|
13
|
-
], InvitationList.prototype, "items", void 0);
|
|
14
|
-
tslib_1.__decorate([
|
|
15
|
-
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'The total number of invitations.' }),
|
|
16
|
-
tslib_1.__metadata("design:type", Number)
|
|
17
|
-
], InvitationList.prototype, "total", void 0);
|
|
18
|
-
exports.InvitationList = InvitationList = tslib_1.__decorate([
|
|
19
|
-
(0, type_graphql_1.ObjectType)({ description: 'A paginated list of invitations.' })
|
|
20
|
-
], InvitationList);
|
|
21
|
-
//# sourceMappingURL=invitation-types.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invitation-types.js","sourceRoot":"","sources":["../../../server/service/invitation/invitation-types.ts"],"names":[],"mappings":";;;;AAAA,mDAA4C;AAC5C,+CAAqD;AAG9C,IAAM,cAAc,GAApB,MAAM,cAAc;CAM1B,CAAA;AANY,wCAAc;AAEzB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,0BAAU,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;;6CAC3E;AAGnB;IADC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC;;6CAC3E;yBALF,cAAc;IAD1B,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,kCAAkC,EAAE,CAAC;GACnD,cAAc,CAM1B","sourcesContent":["import { Invitation } from './invitation.js'\nimport { ObjectType, Field, Int } from 'type-graphql'\n\n@ObjectType({ description: 'A paginated list of invitations.' })\nexport class InvitationList {\n @Field(type => [Invitation], { nullable: true, description: 'The list of invitation items.' })\n items: Invitation[]\n\n @Field(type => Int, { nullable: true, description: 'The total number of invitations.' })\n total: number\n}\n"]}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Domain } from '@things-factory/shell';
|
|
2
|
-
import { Role } from '../role/role.js';
|
|
3
|
-
import { User } from '../user/user.js';
|
|
4
|
-
import { InvitationStatus } from '../../utils/invitation-state.js';
|
|
5
|
-
export declare class Invitation {
|
|
6
|
-
readonly id: string;
|
|
7
|
-
email: string;
|
|
8
|
-
domain: Domain;
|
|
9
|
-
domainId: string;
|
|
10
|
-
/** ⚠ Superseded by `domain`. Kept nullable for rows written before that column existed. */
|
|
11
|
-
reference?: string;
|
|
12
|
-
/** ⚠ Superseded by `domain`. Kept nullable for rows written before that column existed. */
|
|
13
|
-
type?: string;
|
|
14
|
-
roles?: Role[];
|
|
15
|
-
status: InvitationStatus;
|
|
16
|
-
expiresAt?: Date;
|
|
17
|
-
acceptedAt?: Date;
|
|
18
|
-
token: string;
|
|
19
|
-
state?: string;
|
|
20
|
-
createdAt: Date;
|
|
21
|
-
updatedAt: Date;
|
|
22
|
-
creator: User;
|
|
23
|
-
creatorId: string;
|
|
24
|
-
updater: User;
|
|
25
|
-
updaterId: string;
|
|
26
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Invitation = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const typeorm_1 = require("typeorm");
|
|
6
|
-
const type_graphql_1 = require("type-graphql");
|
|
7
|
-
const graphql_scalars_1 = require("graphql-scalars");
|
|
8
|
-
const env_1 = require("@things-factory/env");
|
|
9
|
-
const shell_1 = require("@things-factory/shell");
|
|
10
|
-
const role_js_1 = require("../role/role.js");
|
|
11
|
-
const user_js_1 = require("../user/user.js");
|
|
12
|
-
const invitation_state_js_1 = require("../../utils/invitation-state.js");
|
|
13
|
-
const ORMCONFIG = env_1.config.get('ormconfig', {});
|
|
14
|
-
const DATABASE_TYPE = ORMCONFIG.type;
|
|
15
|
-
/*
|
|
16
|
-
* ── Why there is no unique index on (domain, email) ─────────────────────────
|
|
17
|
-
* There was one on `(email, reference, type)`, which meant a person could be invited to a
|
|
18
|
-
* domain **once, ever**. That was consistent with a table whose rows were deleted on accept —
|
|
19
|
-
* it held pending invitations and nothing else. Now the rows stay, so "invited last year, left,
|
|
20
|
-
* invited again" has to be expressible, and a unique constraint across the history forbids it.
|
|
21
|
-
*
|
|
22
|
-
* What still has to hold is **at most one pending invitation** per person and domain. A partial
|
|
23
|
-
* unique index would say that, and partial indexes are not portable across the databases this
|
|
24
|
-
* framework supports (`reference_typeorm_column_type_portability`). So it is enforced where the
|
|
25
|
-
* invitation is created, inside the same transaction, and the index below is for reading the
|
|
26
|
-
* list.
|
|
27
|
-
*/
|
|
28
|
-
let Invitation = class Invitation {
|
|
29
|
-
};
|
|
30
|
-
exports.Invitation = Invitation;
|
|
31
|
-
tslib_1.__decorate([
|
|
32
|
-
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
33
|
-
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Unique identifier for the invitation.' }),
|
|
34
|
-
tslib_1.__metadata("design:type", String)
|
|
35
|
-
], Invitation.prototype, "id", void 0);
|
|
36
|
-
tslib_1.__decorate([
|
|
37
|
-
(0, typeorm_1.Column)(),
|
|
38
|
-
(0, type_graphql_1.Field)(type => graphql_scalars_1.GraphQLEmailAddress, { description: 'The email address to which the invitation is sent.' }),
|
|
39
|
-
tslib_1.__metadata("design:type", String)
|
|
40
|
-
], Invitation.prototype, "email", void 0);
|
|
41
|
-
tslib_1.__decorate([
|
|
42
|
-
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
43
|
-
(0, type_graphql_1.Field)(type => shell_1.Domain, { description: 'The domain the user is invited to join.' }),
|
|
44
|
-
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
45
|
-
], Invitation.prototype, "domain", void 0);
|
|
46
|
-
tslib_1.__decorate([
|
|
47
|
-
(0, typeorm_1.RelationId)((invitation) => invitation.domain),
|
|
48
|
-
tslib_1.__metadata("design:type", String)
|
|
49
|
-
], Invitation.prototype, "domainId", void 0);
|
|
50
|
-
tslib_1.__decorate([
|
|
51
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
52
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'Deprecated. The domain the invitation is to is `domain`.' }),
|
|
53
|
-
tslib_1.__metadata("design:type", String)
|
|
54
|
-
], Invitation.prototype, "reference", void 0);
|
|
55
|
-
tslib_1.__decorate([
|
|
56
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
57
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'Deprecated. See `reference`.' }),
|
|
58
|
-
tslib_1.__metadata("design:type", String)
|
|
59
|
-
], Invitation.prototype, "type", void 0);
|
|
60
|
-
tslib_1.__decorate([
|
|
61
|
-
(0, typeorm_1.ManyToMany)(type => role_js_1.Role),
|
|
62
|
-
(0, typeorm_1.JoinTable)({ name: 'invitations_roles' }),
|
|
63
|
-
(0, type_graphql_1.Field)(type => [role_js_1.Role], { nullable: true, description: 'Roles to grant when the invitation is accepted.' }),
|
|
64
|
-
tslib_1.__metadata("design:type", Array)
|
|
65
|
-
], Invitation.prototype, "roles", void 0);
|
|
66
|
-
tslib_1.__decorate([
|
|
67
|
-
(0, typeorm_1.Column)({
|
|
68
|
-
type: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
69
|
-
? 'enum'
|
|
70
|
-
: DATABASE_TYPE == 'oracle'
|
|
71
|
-
? 'varchar2'
|
|
72
|
-
: DATABASE_TYPE == 'mssql'
|
|
73
|
-
? 'nvarchar'
|
|
74
|
-
: 'varchar',
|
|
75
|
-
enum: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
|
|
76
|
-
? invitation_state_js_1.InvitationStatus
|
|
77
|
-
: undefined,
|
|
78
|
-
length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,
|
|
79
|
-
default: invitation_state_js_1.InvitationStatus.PENDING
|
|
80
|
-
}),
|
|
81
|
-
(0, type_graphql_1.Field)(type => String, { description: 'pending, accepted, cancelled or expired.' }),
|
|
82
|
-
tslib_1.__metadata("design:type", String)
|
|
83
|
-
], Invitation.prototype, "status", void 0);
|
|
84
|
-
tslib_1.__decorate([
|
|
85
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
86
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'When this invitation stops being acceptable.' }),
|
|
87
|
-
tslib_1.__metadata("design:type", Date)
|
|
88
|
-
], Invitation.prototype, "expiresAt", void 0);
|
|
89
|
-
tslib_1.__decorate([
|
|
90
|
-
(0, typeorm_1.Column)({ nullable: true }),
|
|
91
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'When the invitation was accepted.' }),
|
|
92
|
-
tslib_1.__metadata("design:type", Date
|
|
93
|
-
/*
|
|
94
|
-
* ⚠ Never returned to a client. The token is what opens the door, and an invitation list is a
|
|
95
|
-
* screen an administrator reads — printing it there would hand every pending invitation to
|
|
96
|
-
* whoever can see the list. `invitation-query.ts` does not resolve it.
|
|
97
|
-
*/
|
|
98
|
-
)
|
|
99
|
-
], Invitation.prototype, "acceptedAt", void 0);
|
|
100
|
-
tslib_1.__decorate([
|
|
101
|
-
(0, typeorm_1.Column)(),
|
|
102
|
-
tslib_1.__metadata("design:type", String)
|
|
103
|
-
], Invitation.prototype, "token", void 0);
|
|
104
|
-
tslib_1.__decorate([
|
|
105
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'pending, accepted, cancelled or expired — read as of now.' }),
|
|
106
|
-
tslib_1.__metadata("design:type", String)
|
|
107
|
-
], Invitation.prototype, "state", void 0);
|
|
108
|
-
tslib_1.__decorate([
|
|
109
|
-
(0, typeorm_1.CreateDateColumn)({ nullable: true }),
|
|
110
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'The timestamp when the invitation was created.' }),
|
|
111
|
-
tslib_1.__metadata("design:type", Date)
|
|
112
|
-
], Invitation.prototype, "createdAt", void 0);
|
|
113
|
-
tslib_1.__decorate([
|
|
114
|
-
(0, typeorm_1.UpdateDateColumn)({ nullable: true }),
|
|
115
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'The timestamp when the invitation was last updated.' }),
|
|
116
|
-
tslib_1.__metadata("design:type", Date)
|
|
117
|
-
], Invitation.prototype, "updatedAt", void 0);
|
|
118
|
-
tslib_1.__decorate([
|
|
119
|
-
(0, typeorm_1.ManyToOne)(type => user_js_1.User, { nullable: true }),
|
|
120
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'The user who created the invitation.' }),
|
|
121
|
-
tslib_1.__metadata("design:type", user_js_1.User)
|
|
122
|
-
], Invitation.prototype, "creator", void 0);
|
|
123
|
-
tslib_1.__decorate([
|
|
124
|
-
(0, typeorm_1.RelationId)((invitation) => invitation.creator),
|
|
125
|
-
tslib_1.__metadata("design:type", String)
|
|
126
|
-
], Invitation.prototype, "creatorId", void 0);
|
|
127
|
-
tslib_1.__decorate([
|
|
128
|
-
(0, typeorm_1.ManyToOne)(type => user_js_1.User, { nullable: true }),
|
|
129
|
-
(0, type_graphql_1.Field)({ nullable: true, description: 'The user who last updated the invitation.' }),
|
|
130
|
-
tslib_1.__metadata("design:type", user_js_1.User)
|
|
131
|
-
], Invitation.prototype, "updater", void 0);
|
|
132
|
-
tslib_1.__decorate([
|
|
133
|
-
(0, typeorm_1.RelationId)((invitation) => invitation.updater),
|
|
134
|
-
tslib_1.__metadata("design:type", String)
|
|
135
|
-
], Invitation.prototype, "updaterId", void 0);
|
|
136
|
-
exports.Invitation = Invitation = tslib_1.__decorate([
|
|
137
|
-
(0, typeorm_1.Entity)(),
|
|
138
|
-
(0, typeorm_1.Index)('ix_invitation_0', (invitation) => [invitation.domain, invitation.email]),
|
|
139
|
-
(0, typeorm_1.Index)('ix_invitation_1', (invitation) => [invitation.token], { unique: true }),
|
|
140
|
-
(0, type_graphql_1.ObjectType)({ description: 'Represents an invitation for a user to join a system or a specific resource.' })
|
|
141
|
-
], Invitation);
|
|
142
|
-
//# sourceMappingURL=invitation.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invitation.js","sourceRoot":"","sources":["../../../server/service/invitation/invitation.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CreateDateColumn,\n UpdateDateColumn,\n Entity,\n Index,\n Column,\n JoinTable,\n ManyToMany,\n ManyToOne,\n RelationId,\n PrimaryGeneratedColumn\n} from 'typeorm'\nimport { ObjectType, Field, ID } from 'type-graphql'\nimport { GraphQLEmailAddress } from 'graphql-scalars'\nimport { config } from '@things-factory/env'\nimport { Domain } from '@things-factory/shell'\n\nimport { Role } from '../role/role.js'\nimport { User } from '../user/user.js'\nimport { InvitationStatus } from '../../utils/invitation-state.js'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n/*\n * ── Why there is no unique index on (domain, email) ─────────────────────────\n * There was one on `(email, reference, type)`, which meant a person could be invited to a\n * domain **once, ever**. That was consistent with a table whose rows were deleted on accept —\n * it held pending invitations and nothing else. Now the rows stay, so \"invited last year, left,\n * invited again\" has to be expressible, and a unique constraint across the history forbids it.\n *\n * What still has to hold is **at most one pending invitation** per person and domain. A partial\n * unique index would say that, and partial indexes are not portable across the databases this\n * framework supports (`reference_typeorm_column_type_portability`). So it is enforced where the\n * invitation is created, inside the same transaction, and the index below is for reading the\n * list.\n */\n@Entity()\n@Index('ix_invitation_0', (invitation: Invitation) => [invitation.domain, invitation.email])\n@Index('ix_invitation_1', (invitation: Invitation) => [invitation.token], { unique: true })\n@ObjectType({ description: 'Represents an invitation for a user to join a system or a specific resource.' })\nexport class Invitation {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Unique identifier for the invitation.' })\n readonly id: string\n\n @Column()\n @Field(type => GraphQLEmailAddress, { description: 'The email address to which the invitation is sent.' })\n email: string\n\n /*\n * The domain this invitation is to.\n *\n * ⚠ `reference` below used to carry this. Every live use read it as a domain id\n * (`Domain.findOneBy({ id: reference })`), and the line above the first of them said\n * `// TODO reference should not be a domain.id (security reason)` — so the code was doing the\n * thing its own author had written down as wrong. The column says it now, which is what that\n * note was asking for, and it is what the list is scoped by.\n */\n @ManyToOne(type => Domain)\n @Field(type => Domain, { description: 'The domain the user is invited to join.' })\n domain: Domain\n\n @RelationId((invitation: Invitation) => invitation.domain)\n domainId: string\n\n /** ⚠ Superseded by `domain`. Kept nullable for rows written before that column existed. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Deprecated. The domain the invitation is to is `domain`.' })\n reference?: string\n\n /** ⚠ Superseded by `domain`. Kept nullable for rows written before that column existed. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Deprecated. See `reference`.' })\n type?: string\n\n /*\n * The roles this person gets on arrival.\n *\n * Without them somebody accepts an invitation, signs in, and has no screens at all — the\n * `user-invite` screen already decided that roles are chosen where the person is invited, for\n * exactly that reason. An invitation is that place for the people who arrive this way.\n */\n @ManyToMany(type => Role)\n @JoinTable({ name: 'invitations_roles' })\n @Field(type => [Role], { nullable: true, description: 'Roles to grant when the invitation is accepted.' })\n roles?: Role[]\n\n @Column({\n type:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'enum'\n : DATABASE_TYPE == 'oracle'\n ? 'varchar2'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'varchar',\n enum:\n DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? InvitationStatus\n : undefined,\n length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,\n default: InvitationStatus.PENDING\n })\n @Field(type => String, { description: 'pending, accepted, cancelled or expired.' })\n status: InvitationStatus\n\n /*\n * ⚠ Nullable only because rows written before this column existed have nothing here, and those\n * read as expired (`utils/invitation-state.ts`). Everything written from now on sets it.\n */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When this invitation stops being acceptable.' })\n expiresAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When the invitation was accepted.' })\n acceptedAt?: Date\n\n /*\n * ⚠ Never returned to a client. The token is what opens the door, and an invitation list is a\n * screen an administrator reads — printing it there would hand every pending invitation to\n * whoever can see the list. `invitation-query.ts` does not resolve it.\n */\n @Column()\n token: string\n\n /* Resolved against the clock in `invitation-query.ts`, not stored on its own. */\n @Field({ nullable: true, description: 'pending, accepted, cancelled or expired — read as of now.' })\n state?: string\n\n @CreateDateColumn({ nullable: true })\n @Field({ nullable: true, description: 'The timestamp when the invitation was created.' })\n createdAt: Date\n\n @UpdateDateColumn({ nullable: true })\n @Field({ nullable: true, description: 'The timestamp when the invitation was last updated.' })\n updatedAt: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who created the invitation.' })\n creator: User\n\n @RelationId((invitation: Invitation) => invitation.creator)\n creatorId: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field({ nullable: true, description: 'The user who last updated the invitation.' })\n updater: User\n\n @RelationId((invitation: Invitation) => invitation.updater)\n updaterId: string\n}\n"]}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getInvitationEmailForm = getInvitationEmailForm;
|
|
4
|
-
/*
|
|
5
|
-
* The link carries the token in its fragment, which needs JavaScript to read. The same token is
|
|
6
|
-
* printed as text so somebody whose mail client or browser will not follow it can paste it into
|
|
7
|
-
* the page instead.
|
|
8
|
-
*/
|
|
9
|
-
function getInvitationEmailForm({ username, email, acceptUrl, code }) {
|
|
10
|
-
return `
|
|
11
|
-
<html lang="en">
|
|
12
|
-
<head>
|
|
13
|
-
<meta charset="utf-8" />
|
|
14
|
-
|
|
15
|
-
<title>Invitation from Operato</title>
|
|
16
|
-
<meta name="description" content="Invitation" />
|
|
17
|
-
<meta name="author" content="hatiolab" />
|
|
18
|
-
<meta name="google" content="notranslate"/>
|
|
19
|
-
</head>
|
|
20
|
-
|
|
21
|
-
<body>
|
|
22
|
-
<div style="background-color:#f6f6f6">
|
|
23
|
-
<!--header begin-->
|
|
24
|
-
<div style="background-color:#fff;padding:0 10px;border-top: 2px solid #394e64;">
|
|
25
|
-
<a href="#" target="_blank"
|
|
26
|
-
><img
|
|
27
|
-
src="http://www.hatiolab.com/assets/img/logo-operato.png"
|
|
28
|
-
style="max-height:50px"
|
|
29
|
-
/></a>
|
|
30
|
-
</div>
|
|
31
|
-
<!--header end-->
|
|
32
|
-
|
|
33
|
-
<!--title begin-->
|
|
34
|
-
<div
|
|
35
|
-
style="background-color:#22a6a7;padding:12px 10px 10px 10px;min-height:50px;"
|
|
36
|
-
>
|
|
37
|
-
<img
|
|
38
|
-
src="http://www.hatiolab.com/assets/img/icon-mail.png"
|
|
39
|
-
style="float:left;margin:0 10px 0 40px"
|
|
40
|
-
/>
|
|
41
|
-
<span style="display:block;color:#fff;font-size:20px"
|
|
42
|
-
>Hi ${email}!</span
|
|
43
|
-
>
|
|
44
|
-
<span style="display:block;color:#fff;font-size:34px;font-weight:bold"
|
|
45
|
-
>Verify your email</span
|
|
46
|
-
>
|
|
47
|
-
</div>
|
|
48
|
-
<!--title end-->
|
|
49
|
-
|
|
50
|
-
<!--body begin-->
|
|
51
|
-
<p style="padding:10px 20px;line-height:1.5;font-size:16px">
|
|
52
|
-
You have been invited to join Operato.
|
|
53
|
-
Use the button below to accept, and you will be asked for this email address to confirm
|
|
54
|
-
it reached the right person.
|
|
55
|
-
<br />
|
|
56
|
-
<a
|
|
57
|
-
href="${acceptUrl}"
|
|
58
|
-
style="display:inline-block;margin:10px 5px 5px 0;border-radius:7px;background-color:#22a6a7;padding:7px 15px;color:#fff;font-size:18px;text-decoration:none;text-transform:capitalize;"
|
|
59
|
-
>Accept invitation</a
|
|
60
|
-
>
|
|
61
|
-
</p>
|
|
62
|
-
|
|
63
|
-
<p style="padding:0 20px 10px 20px;line-height:1.5;font-size:13px;color:#666">
|
|
64
|
-
If the button does not work, open the invitation page and paste this code:
|
|
65
|
-
<br />
|
|
66
|
-
<span style="font-family:monospace;font-size:15px;color:#222">${code}</span>
|
|
67
|
-
</p>
|
|
68
|
-
<!--body end-->
|
|
69
|
-
|
|
70
|
-
<!--footer begin-->
|
|
71
|
-
<div
|
|
72
|
-
style="background-color:#3d5874;padding:7px 20px 5px 20px;font-size:12px;color:#efefef"
|
|
73
|
-
>
|
|
74
|
-
© Hatio, Lab. Inc. All rights reserved.
|
|
75
|
-
</div>
|
|
76
|
-
<!--footer end-->
|
|
77
|
-
</div>
|
|
78
|
-
</body>
|
|
79
|
-
</html>
|
|
80
|
-
`;
|
|
81
|
-
}
|
|
82
|
-
//# sourceMappingURL=invitation-email.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invitation-email.js","sourceRoot":"","sources":["../../server/templates/invitation-email.ts"],"names":[],"mappings":";;AAKA,wDAwEC;AA7ED;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE;IACzE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAgCS,KAAK;;;;;;;;;;;;;;;oBAeH,SAAS;;;;;;;;;0EAS6C,IAAI;;;;;;;;;;;;;;GAc3E,CAAA;AACH,CAAC","sourcesContent":["/*\n * The link carries the token in its fragment, which needs JavaScript to read. The same token is\n * printed as text so somebody whose mail client or browser will not follow it can paste it into\n * the page instead.\n */\nexport function getInvitationEmailForm({ username, email, acceptUrl, code }) {\n return `\n <html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n \n <title>Invitation from Operato</title>\n <meta name=\"description\" content=\"Invitation\" />\n <meta name=\"author\" content=\"hatiolab\" />\n <meta name=\"google\" content=\"notranslate\"/>\n </head>\n\n <body>\n <div style=\"background-color:#f6f6f6\">\n <!--header begin-->\n <div style=\"background-color:#fff;padding:0 10px;border-top: 2px solid #394e64;\">\n <a href=\"#\" target=\"_blank\"\n ><img\n src=\"http://www.hatiolab.com/assets/img/logo-operato.png\"\n style=\"max-height:50px\"\n /></a>\n </div>\n <!--header end-->\n\n <!--title begin-->\n <div\n style=\"background-color:#22a6a7;padding:12px 10px 10px 10px;min-height:50px;\"\n >\n <img\n src=\"http://www.hatiolab.com/assets/img/icon-mail.png\"\n style=\"float:left;margin:0 10px 0 40px\"\n />\n <span style=\"display:block;color:#fff;font-size:20px\"\n >Hi ${email}!</span\n >\n <span style=\"display:block;color:#fff;font-size:34px;font-weight:bold\"\n >Verify your email</span\n >\n </div>\n <!--title end-->\n\n <!--body begin-->\n <p style=\"padding:10px 20px;line-height:1.5;font-size:16px\">\n You have been invited to join Operato.\n Use the button below to accept, and you will be asked for this email address to confirm\n it reached the right person.\n <br />\n <a\n href=\"${acceptUrl}\"\n style=\"display:inline-block;margin:10px 5px 5px 0;border-radius:7px;background-color:#22a6a7;padding:7px 15px;color:#fff;font-size:18px;text-decoration:none;text-transform:capitalize;\"\n >Accept invitation</a\n >\n </p>\n\n <p style=\"padding:0 20px 10px 20px;line-height:1.5;font-size:13px;color:#666\">\n If the button does not work, open the invitation page and paste this code:\n <br />\n <span style=\"font-family:monospace;font-size:15px;color:#222\">${code}</span>\n </p>\n <!--body end-->\n\n <!--footer begin-->\n <div\n style=\"background-color:#3d5874;padding:7px 20px 5px 20px;font-size:12px;color:#efefef\"\n >\n © Hatio, Lab. Inc. All rights reserved.\n </div>\n <!--footer end-->\n </div>\n </body>\n </html>\n `\n}\n"]}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What state an invitation is in, and whether it can still be accepted. (Pure rule.)
|
|
3
|
-
*
|
|
4
|
-
* ## Why the table keeps rows now
|
|
5
|
-
*
|
|
6
|
-
* Accepting used to delete the row and cancelling still does, so the table was a queue of
|
|
7
|
-
* pending invitations and nothing else. The moment somebody joined, the fact that they had been
|
|
8
|
-
* invited stopped existing — which is why no screen was ever built on it, and why nothing calls
|
|
9
|
-
* any of it. An invitation list has to be able to say *who was invited, by whom, when, and
|
|
10
|
-
* whether they came in*.
|
|
11
|
-
*
|
|
12
|
-
* So the row survives and carries its state. `pending` → one of the other three, and never back.
|
|
13
|
-
*
|
|
14
|
-
* ## Why expiry is derived and not only stored
|
|
15
|
-
*
|
|
16
|
-
* A token that outlives its invitation is a credential nobody is watching. If `expired` were
|
|
17
|
-
* only ever a written value, it would depend on something running to write it, and the gap
|
|
18
|
-
* between the expiry passing and that thing running is a window where the token still works.
|
|
19
|
-
* So the rule reads the clock: a pending row past its `expiresAt` **is** expired, whatever the
|
|
20
|
-
* column says. Writing the status is then bookkeeping for the list, not the guard.
|
|
21
|
-
*
|
|
22
|
-
* ## What an absent value means, and why it refuses
|
|
23
|
-
*
|
|
24
|
-
* Rows written before this existed have neither status nor expiry. They come from a flow that
|
|
25
|
-
* never completed — the link in the email led nowhere — so treating them as live would keep
|
|
26
|
-
* tokens alive that were never usable in the first place.
|
|
27
|
-
*
|
|
28
|
-
* status absent reads as `pending`, which is what the old table meant by a row existing
|
|
29
|
-
* expiresAt absent reads as **expired**. An invitation that cannot say when it dies is not
|
|
30
|
-
* a live invitation, and this is a credential: absence refuses.
|
|
31
|
-
*/
|
|
32
|
-
/** The states an invitation row can be in. Stored, and also derived for expiry. */
|
|
33
|
-
export declare enum InvitationStatus {
|
|
34
|
-
PENDING = "pending",
|
|
35
|
-
ACCEPTED = "accepted",
|
|
36
|
-
CANCELLED = "cancelled",
|
|
37
|
-
EXPIRED = "expired"
|
|
38
|
-
}
|
|
39
|
-
/** The parts of an invitation this rule reads. */
|
|
40
|
-
export type InvitationRecord = {
|
|
41
|
-
status?: string | null;
|
|
42
|
-
expiresAt?: Date | string | null;
|
|
43
|
-
} | null | undefined;
|
|
44
|
-
/**
|
|
45
|
-
* The state this invitation is in, as of `now`.
|
|
46
|
-
*
|
|
47
|
-
* Only a pending row is measured against the clock. Once a row has been accepted or cancelled
|
|
48
|
-
* that is what happened to it, and time passing does not rewrite history.
|
|
49
|
-
*/
|
|
50
|
-
export declare function invitationState(invitation: InvitationRecord, now?: Date): InvitationStatus;
|
|
51
|
-
/** May this invitation still be accepted? Only a pending one that has not run out. */
|
|
52
|
-
export declare function mayAccept(invitation: InvitationRecord, now?: Date): boolean;
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* What state an invitation is in, and whether it can still be accepted. (Pure rule.)
|
|
4
|
-
*
|
|
5
|
-
* ## Why the table keeps rows now
|
|
6
|
-
*
|
|
7
|
-
* Accepting used to delete the row and cancelling still does, so the table was a queue of
|
|
8
|
-
* pending invitations and nothing else. The moment somebody joined, the fact that they had been
|
|
9
|
-
* invited stopped existing — which is why no screen was ever built on it, and why nothing calls
|
|
10
|
-
* any of it. An invitation list has to be able to say *who was invited, by whom, when, and
|
|
11
|
-
* whether they came in*.
|
|
12
|
-
*
|
|
13
|
-
* So the row survives and carries its state. `pending` → one of the other three, and never back.
|
|
14
|
-
*
|
|
15
|
-
* ## Why expiry is derived and not only stored
|
|
16
|
-
*
|
|
17
|
-
* A token that outlives its invitation is a credential nobody is watching. If `expired` were
|
|
18
|
-
* only ever a written value, it would depend on something running to write it, and the gap
|
|
19
|
-
* between the expiry passing and that thing running is a window where the token still works.
|
|
20
|
-
* So the rule reads the clock: a pending row past its `expiresAt` **is** expired, whatever the
|
|
21
|
-
* column says. Writing the status is then bookkeeping for the list, not the guard.
|
|
22
|
-
*
|
|
23
|
-
* ## What an absent value means, and why it refuses
|
|
24
|
-
*
|
|
25
|
-
* Rows written before this existed have neither status nor expiry. They come from a flow that
|
|
26
|
-
* never completed — the link in the email led nowhere — so treating them as live would keep
|
|
27
|
-
* tokens alive that were never usable in the first place.
|
|
28
|
-
*
|
|
29
|
-
* status absent reads as `pending`, which is what the old table meant by a row existing
|
|
30
|
-
* expiresAt absent reads as **expired**. An invitation that cannot say when it dies is not
|
|
31
|
-
* a live invitation, and this is a credential: absence refuses.
|
|
32
|
-
*/
|
|
33
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
-
exports.InvitationStatus = void 0;
|
|
35
|
-
exports.invitationState = invitationState;
|
|
36
|
-
exports.mayAccept = mayAccept;
|
|
37
|
-
/** The states an invitation row can be in. Stored, and also derived for expiry. */
|
|
38
|
-
var InvitationStatus;
|
|
39
|
-
(function (InvitationStatus) {
|
|
40
|
-
InvitationStatus["PENDING"] = "pending";
|
|
41
|
-
InvitationStatus["ACCEPTED"] = "accepted";
|
|
42
|
-
InvitationStatus["CANCELLED"] = "cancelled";
|
|
43
|
-
InvitationStatus["EXPIRED"] = "expired";
|
|
44
|
-
})(InvitationStatus || (exports.InvitationStatus = InvitationStatus = {}));
|
|
45
|
-
function expiryPassed(expiresAt, now) {
|
|
46
|
-
if (!expiresAt) {
|
|
47
|
-
/* No expiry recorded — see the note above. Absence refuses. */
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
const at = expiresAt instanceof Date ? expiresAt : new Date(expiresAt);
|
|
51
|
-
if (Number.isNaN(at.getTime())) {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
return at.getTime() <= now.getTime();
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* The state this invitation is in, as of `now`.
|
|
58
|
-
*
|
|
59
|
-
* Only a pending row is measured against the clock. Once a row has been accepted or cancelled
|
|
60
|
-
* that is what happened to it, and time passing does not rewrite history.
|
|
61
|
-
*/
|
|
62
|
-
function invitationState(invitation, now = new Date()) {
|
|
63
|
-
const status = invitation?.status || InvitationStatus.PENDING;
|
|
64
|
-
if (status === InvitationStatus.ACCEPTED || status === InvitationStatus.CANCELLED) {
|
|
65
|
-
return status;
|
|
66
|
-
}
|
|
67
|
-
if (status === InvitationStatus.EXPIRED) {
|
|
68
|
-
return InvitationStatus.EXPIRED;
|
|
69
|
-
}
|
|
70
|
-
return expiryPassed(invitation?.expiresAt, now) ? InvitationStatus.EXPIRED : InvitationStatus.PENDING;
|
|
71
|
-
}
|
|
72
|
-
/** May this invitation still be accepted? Only a pending one that has not run out. */
|
|
73
|
-
function mayAccept(invitation, now = new Date()) {
|
|
74
|
-
return !!invitation && invitationState(invitation, now) === InvitationStatus.PENDING;
|
|
75
|
-
}
|
|
76
|
-
//# sourceMappingURL=invitation-state.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"invitation-state.js","sourceRoot":"","sources":["../../server/utils/invitation-state.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAqCH,0CAYC;AAGD,8BAEC;AApDD,mFAAmF;AACnF,IAAY,gBAKX;AALD,WAAY,gBAAgB;IAC1B,uCAAmB,CAAA;IACnB,yCAAqB,CAAA;IACrB,2CAAuB,CAAA;IACvB,uCAAmB,CAAA;AACrB,CAAC,EALW,gBAAgB,gCAAhB,gBAAgB,QAK3B;AAQD,SAAS,YAAY,CAAC,SAA2C,EAAE,GAAS;IAC1E,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,+DAA+D;QAC/D,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,EAAE,GAAG,SAAS,YAAY,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,CAAA;IAEtE,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,EAAE,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,CAAA;AACtC,CAAC;AAED;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,UAA4B,EAAE,MAAY,IAAI,IAAI,EAAE;IAClF,MAAM,MAAM,GAAG,UAAU,EAAE,MAAM,IAAI,gBAAgB,CAAC,OAAO,CAAA;IAE7D,IAAI,MAAM,KAAK,gBAAgB,CAAC,QAAQ,IAAI,MAAM,KAAK,gBAAgB,CAAC,SAAS,EAAE,CAAC;QAClF,OAAO,MAA0B,CAAA;IACnC,CAAC;IAED,IAAI,MAAM,KAAK,gBAAgB,CAAC,OAAO,EAAE,CAAC;QACxC,OAAO,gBAAgB,CAAC,OAAO,CAAA;IACjC,CAAC;IAED,OAAO,YAAY,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAA;AACvG,CAAC;AAED,sFAAsF;AACtF,SAAgB,SAAS,CAAC,UAA4B,EAAE,MAAY,IAAI,IAAI,EAAE;IAC5E,OAAO,CAAC,CAAC,UAAU,IAAI,eAAe,CAAC,UAAU,EAAE,GAAG,CAAC,KAAK,gBAAgB,CAAC,OAAO,CAAA;AACtF,CAAC","sourcesContent":["/**\n * What state an invitation is in, and whether it can still be accepted. (Pure rule.)\n *\n * ## Why the table keeps rows now\n *\n * Accepting used to delete the row and cancelling still does, so the table was a queue of\n * pending invitations and nothing else. The moment somebody joined, the fact that they had been\n * invited stopped existing — which is why no screen was ever built on it, and why nothing calls\n * any of it. An invitation list has to be able to say *who was invited, by whom, when, and\n * whether they came in*.\n *\n * So the row survives and carries its state. `pending` → one of the other three, and never back.\n *\n * ## Why expiry is derived and not only stored\n *\n * A token that outlives its invitation is a credential nobody is watching. If `expired` were\n * only ever a written value, it would depend on something running to write it, and the gap\n * between the expiry passing and that thing running is a window where the token still works.\n * So the rule reads the clock: a pending row past its `expiresAt` **is** expired, whatever the\n * column says. Writing the status is then bookkeeping for the list, not the guard.\n *\n * ## What an absent value means, and why it refuses\n *\n * Rows written before this existed have neither status nor expiry. They come from a flow that\n * never completed — the link in the email led nowhere — so treating them as live would keep\n * tokens alive that were never usable in the first place.\n *\n * status absent reads as `pending`, which is what the old table meant by a row existing\n * expiresAt absent reads as **expired**. An invitation that cannot say when it dies is not\n * a live invitation, and this is a credential: absence refuses.\n */\n\n/** The states an invitation row can be in. Stored, and also derived for expiry. */\nexport enum InvitationStatus {\n PENDING = 'pending',\n ACCEPTED = 'accepted',\n CANCELLED = 'cancelled',\n EXPIRED = 'expired'\n}\n\n/** The parts of an invitation this rule reads. */\nexport type InvitationRecord = {\n status?: string | null\n expiresAt?: Date | string | null\n} | null | undefined\n\nfunction expiryPassed(expiresAt: Date | string | null | undefined, now: Date): boolean {\n if (!expiresAt) {\n /* No expiry recorded — see the note above. Absence refuses. */\n return true\n }\n\n const at = expiresAt instanceof Date ? expiresAt : new Date(expiresAt)\n\n if (Number.isNaN(at.getTime())) {\n return true\n }\n\n return at.getTime() <= now.getTime()\n}\n\n/**\n * The state this invitation is in, as of `now`.\n *\n * Only a pending row is measured against the clock. Once a row has been accepted or cancelled\n * that is what happened to it, and time passing does not rewrite history.\n */\nexport function invitationState(invitation: InvitationRecord, now: Date = new Date()): InvitationStatus {\n const status = invitation?.status || InvitationStatus.PENDING\n\n if (status === InvitationStatus.ACCEPTED || status === InvitationStatus.CANCELLED) {\n return status as InvitationStatus\n }\n\n if (status === InvitationStatus.EXPIRED) {\n return InvitationStatus.EXPIRED\n }\n\n return expiryPassed(invitation?.expiresAt, now) ? InvitationStatus.EXPIRED : InvitationStatus.PENDING\n}\n\n/** May this invitation still be accepted? Only a pending one that has not run out. */\nexport function mayAccept(invitation: InvitationRecord, now: Date = new Date()): boolean {\n return !!invitation && invitationState(invitation, now) === InvitationStatus.PENDING\n}\n"]}
|