@rapidmx/autodiscover-plugin 1.0.0-beta.1
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/LICENSE +382 -0
- package/README.md +90 -0
- package/dist/lib/AutodiscoverXml.js +137 -0
- package/dist/lib/AutodiscoverXml.js.map +1 -0
- package/dist/lib/BaseAutodiscoverRoute.js +205 -0
- package/dist/lib/BaseAutodiscoverRoute.js.map +1 -0
- package/dist/lib/index.js +28 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/mongo/AutodiscoverRouteMongo.js +31 -0
- package/dist/lib/mongo/AutodiscoverRouteMongo.js.map +1 -0
- package/dist/lib/mongo/index.js +6 -0
- package/dist/lib/mongo/index.js.map +1 -0
- package/dist/lib/mongo.js +6 -0
- package/dist/lib/mongo.js.map +1 -0
- package/dist/lib/sql/AutodiscoverRouteSQL.js +39 -0
- package/dist/lib/sql/AutodiscoverRouteSQL.js.map +1 -0
- package/dist/lib/sql/index.js +6 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql.js +6 -0
- package/dist/lib/sql.js.map +1 -0
- package/dist/types/AutodiscoverXml.d.ts +58 -0
- package/dist/types/BaseAutodiscoverRoute.d.ts +79 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/mongo/AutodiscoverRouteMongo.d.ts +11 -0
- package/dist/types/mongo/index.d.ts +1 -0
- package/dist/types/mongo.d.ts +1 -0
- package/dist/types/sql/AutodiscoverRouteSQL.d.ts +15 -0
- package/dist/types/sql/index.d.ts +1 -0
- package/dist/types/sql.d.ts +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
15
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
16
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
17
|
+
import { ObjectDecorators } from "@rapidrest/core";
|
|
18
|
+
import { RepoUtils, RouteDecorators } from "@rapidrest/service-core";
|
|
19
|
+
import { buildOutlookSuccessXml, buildPoxSuccessXml, extractAcceptableResponseSchema, extractEmailAddress, OUTLOOK_RESPONSE_SCHEMA, } from "./AutodiscoverXml.js";
|
|
20
|
+
import { PluginRegistry } from "@rapidmx/restapi";
|
|
21
|
+
const { Config, Init, Logger } = ObjectDecorators;
|
|
22
|
+
/** The plugin packages whose endpoints Autodiscover advertises. */
|
|
23
|
+
export const ACTIVESYNC_PLUGIN = "@rapidmx/activesync";
|
|
24
|
+
export const MAPI_PLUGIN = "@rapidmx/mapi";
|
|
25
|
+
const { Get, Param, Post, Query, Request, Response } = RouteDecorators;
|
|
26
|
+
/**
|
|
27
|
+
* Abstract base for the two Autodiscover endpoints a real mail client uses to find this deployment's EAS
|
|
28
|
+
* server URL from just an email address - classic POX (`POST /autodiscover/autodiscover.xml`, per
|
|
29
|
+
* `[MS-ASCMD]`'s "MobileSync" response schema) and the modern JSON variant Microsoft calls "Autodiscover v2"
|
|
30
|
+
* (`GET /autodiscover/autodiscover.json/v1.0/<email>?Protocol=ActiveSync`). Like `BaseMailIngestRoute`/
|
|
31
|
+
* `BaseEasRoute`, this class is undecorated; its Mongo/SQL concrete classes are mounted at `/autodiscover`, which
|
|
32
|
+
* composes with this class's own relative method paths to land exactly on the real spec's conventional paths (`/autodiscover/autodiscover.xml`, `/autodiscover/autodiscover.json/v1.0/:email`).
|
|
33
|
+
*
|
|
34
|
+
* **Auth: deliberately none.** Real classic Autodiscover conventionally expects the client to send HTTP Basic
|
|
35
|
+
* credentials (email+password), with the server free to answer `401` and force re-entry - a model this
|
|
36
|
+
* library's JWT-only, no-credential-verification-of-our-own boundary (see `BaseEasRoute`'s own doc comment on
|
|
37
|
+
* why per-request Basic Auth was rejected there) can't and shouldn't absorb; no credential-verification
|
|
38
|
+
* function exists in this codebase or its dependencies. Instead, both endpoints here follow Microsoft's own
|
|
39
|
+
* Autodiscover v2 design intent exactly: answer anonymously, and reveal nothing but a deployment-wide,
|
|
40
|
+
* config-supplied EAS server URL - not a per-mailbox secret - once the request email is confirmed to belong to
|
|
41
|
+
* a real `Mailbox` in this deployment. The actual security boundary is unchanged from Phase 2: real mailbox
|
|
42
|
+
* access still requires a JWT at `BaseEasRoute`, exactly as today.
|
|
43
|
+
*
|
|
44
|
+
* **Known gap, deliberately out of scope**: `Action.Redirect` (for multi-tenant hosted providers whose mailbox
|
|
45
|
+
* moved to a different domain) is not implemented - this library serves exactly one EAS URL for its whole
|
|
46
|
+
* deployment, so there's never a different domain to redirect to. The client-side well-known-URL discovery
|
|
47
|
+
* sequence (`MS-OXDISCO`: root domain -> `autodiscover.` subdomain -> unauthenticated HTTP redirect probe ->
|
|
48
|
+
* DNS SRV record -> cache) and the DNS `CNAME`/`SRV` record setup it depends on are also out of scope here -
|
|
49
|
+
* both are client/deployment concerns, not application code; this class only needs to answer correctly once a
|
|
50
|
+
* request actually arrives at one of its two paths.
|
|
51
|
+
*
|
|
52
|
+
* `mailboxClass` is supplied by the Mongo/SQL concrete subclasses following the exact one-line-per-backend
|
|
53
|
+
* pattern used throughout this library. The advertised URLs are built from the `mail:autodiscover:public_url`
|
|
54
|
+
* setting, and each protocol is only advertised while its plugin (`@rapidmx/activesync`, `@rapidmx/mapi`) is
|
|
55
|
+
* loaded, so a client is never pointed at an endpoint this deployment doesn't serve.
|
|
56
|
+
*
|
|
57
|
+
* @author Jean-Philippe Steinmetz
|
|
58
|
+
*/
|
|
59
|
+
export class BaseAutodiscoverRoute {
|
|
60
|
+
constructor() {
|
|
61
|
+
/** The public base URL clients reach this deployment at, e.g. `https://mail.example.com`. */
|
|
62
|
+
this.publicUrl = "";
|
|
63
|
+
}
|
|
64
|
+
/** The EAS endpoint URL to report, or `undefined` when ActiveSync isn't available here. */
|
|
65
|
+
get easUrl() {
|
|
66
|
+
return this.endpointUrl(ACTIVESYNC_PLUGIN, "/Microsoft-Server-ActiveSync");
|
|
67
|
+
}
|
|
68
|
+
/** The MAPI/HTTP `emsmdb` endpoint URL to report to a real Outlook desktop client, or `undefined` when MAPI
|
|
69
|
+
* isn't available here. Only used by `pox()`'s Outlook/EXCH response branch. */
|
|
70
|
+
get mapiUrl() {
|
|
71
|
+
return this.endpointUrl(MAPI_PLUGIN, "/mapi/emsmdb");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Builds the query value used to match `Mailbox.aliasAddresses` against the given address. See
|
|
75
|
+
* `BaseMailIngestRoute.aliasQueryValue()`'s identical doc comment - same Mongo-array vs.
|
|
76
|
+
* SQL-`simple-json`-column backend split, same override point (`AutodiscoverRouteSQL` overrides this
|
|
77
|
+
* identically to `MailIngestRouteSQL`).
|
|
78
|
+
*/
|
|
79
|
+
aliasQueryValue(address) {
|
|
80
|
+
return address;
|
|
81
|
+
}
|
|
82
|
+
endpointUrl(plugin, path) {
|
|
83
|
+
if (!this.publicUrl || !PluginRegistry.isActive(plugin)) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return `${this.publicUrl.replace(/\/+$/, "")}${path}`;
|
|
87
|
+
}
|
|
88
|
+
async init() {
|
|
89
|
+
if (!this.publicUrl) {
|
|
90
|
+
this.logger?.warn("Autodiscover has no mail:autodiscover:public_url set, so it can't point clients at any protocol.");
|
|
91
|
+
}
|
|
92
|
+
this.mailboxRepo = await this._objectFactory.newInstance(RepoUtils, {
|
|
93
|
+
name: this.mailboxClass.name,
|
|
94
|
+
args: [this.mailboxClass],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async resolveMailbox(email) {
|
|
98
|
+
const address = email.trim().toLowerCase();
|
|
99
|
+
const [byPrimary, byAlias] = await Promise.all([
|
|
100
|
+
this.mailboxRepo.find({ primarySmtpAddress: address }, { ignoreACL: true, limit: 1 }),
|
|
101
|
+
this.mailboxRepo.find({ aliasAddresses: this.aliasQueryValue(address) }, { ignoreACL: true, limit: 1 }),
|
|
102
|
+
]);
|
|
103
|
+
return byPrimary[0] ?? byAlias[0];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Classic POX Autodiscover. Per Microsoft's own client-behavior documentation, a permanent failure is just
|
|
107
|
+
* as validly conveyed via a plain HTTP status as via an inner `Error` element, so this uses the HTTP-status
|
|
108
|
+
* form for both error cases rather than inventing values for `[MS-ASCMD]`'s provider-specific numeric
|
|
109
|
+
* error-code table.
|
|
110
|
+
*
|
|
111
|
+
* Branches on the request's `AcceptableResponseSchema` to decide which response shape to build: a real
|
|
112
|
+
* Outlook desktop client sends `[MS-OXDSCLI]`'s Outlook/EXCH schema (`OUTLOOK_RESPONSE_SCHEMA`) to locate
|
|
113
|
+
* this deployment's MAPI/HTTP endpoint (`buildOutlookSuccessXml`); any other value (or a mobile/EAS-only
|
|
114
|
+
* client that omits the field entirely) gets the original MobileSync/EAS response (`buildPoxSuccessXml`).
|
|
115
|
+
*/
|
|
116
|
+
async pox(req, res) {
|
|
117
|
+
if (!this.mailboxRepo) {
|
|
118
|
+
res.status(500).send();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const body = req.rawBody ? req.rawBody.toString("utf-8") : "";
|
|
122
|
+
const email = extractEmailAddress(body);
|
|
123
|
+
if (!email) {
|
|
124
|
+
res.status(400).send();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const mailbox = await this.resolveMailbox(email);
|
|
128
|
+
if (!mailbox) {
|
|
129
|
+
res.status(404).send();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const outlook = extractAcceptableResponseSchema(body) === OUTLOOK_RESPONSE_SCHEMA;
|
|
133
|
+
const url = outlook ? this.mapiUrl : this.easUrl;
|
|
134
|
+
if (!url) {
|
|
135
|
+
// The protocol this client asked about isn't served here - a 404 lets it move on to its next discovery step.
|
|
136
|
+
res.status(404).send();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const xml = outlook
|
|
140
|
+
? buildOutlookSuccessXml({ emailAddress: email, displayName: mailbox.displayName, mapiUrl: url })
|
|
141
|
+
: buildPoxSuccessXml({ emailAddress: email, displayName: mailbox.displayName, easUrl: url });
|
|
142
|
+
res.setHeader("Content-Type", "application/xml; charset=utf-8").status(200).send(xml);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Autodiscover v2 (JSON). Only the `ActiveSync` protocol is served - this library has no EWS/other
|
|
146
|
+
* protocol surface to advertise - so any other `Protocol` value is rejected outright rather than silently
|
|
147
|
+
* answered with an EAS URL under the wrong protocol name.
|
|
148
|
+
*/
|
|
149
|
+
async v2(email, protocol, res) {
|
|
150
|
+
if (!this.mailboxRepo) {
|
|
151
|
+
res.status(500).send();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// An unsupported protocol and ActiveSync not being available here get the same answer.
|
|
155
|
+
const easUrl = this.easUrl;
|
|
156
|
+
if (protocol !== "ActiveSync" || !easUrl) {
|
|
157
|
+
res.status(400).json({
|
|
158
|
+
ErrorCode: "ProtocolNotSupported",
|
|
159
|
+
ErrorMessage: `Unsupported protocol: ${protocol ?? ""}`,
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const mailbox = await this.resolveMailbox(decodeURIComponent(email));
|
|
164
|
+
if (!mailbox) {
|
|
165
|
+
res.status(404).json({
|
|
166
|
+
ErrorCode: "UserNotFound",
|
|
167
|
+
ErrorMessage: "No mailbox exists for the given address.",
|
|
168
|
+
});
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
res.status(200).json({ Protocol: "ActiveSync", Url: easUrl });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
__decorate([
|
|
175
|
+
Config("mail:autodiscover:public_url", ""),
|
|
176
|
+
__metadata("design:type", String)
|
|
177
|
+
], BaseAutodiscoverRoute.prototype, "publicUrl", void 0);
|
|
178
|
+
__decorate([
|
|
179
|
+
Logger,
|
|
180
|
+
__metadata("design:type", Object)
|
|
181
|
+
], BaseAutodiscoverRoute.prototype, "logger", void 0);
|
|
182
|
+
__decorate([
|
|
183
|
+
Init,
|
|
184
|
+
__metadata("design:type", Function),
|
|
185
|
+
__metadata("design:paramtypes", []),
|
|
186
|
+
__metadata("design:returntype", Promise)
|
|
187
|
+
], BaseAutodiscoverRoute.prototype, "init", null);
|
|
188
|
+
__decorate([
|
|
189
|
+
Post("/autodiscover.xml"),
|
|
190
|
+
__param(0, Request),
|
|
191
|
+
__param(1, Response),
|
|
192
|
+
__metadata("design:type", Function),
|
|
193
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
194
|
+
__metadata("design:returntype", Promise)
|
|
195
|
+
], BaseAutodiscoverRoute.prototype, "pox", null);
|
|
196
|
+
__decorate([
|
|
197
|
+
Get("/autodiscover.json/v1.0/:email"),
|
|
198
|
+
__param(0, Param("email")),
|
|
199
|
+
__param(1, Query("Protocol")),
|
|
200
|
+
__param(2, Response),
|
|
201
|
+
__metadata("design:type", Function),
|
|
202
|
+
__metadata("design:paramtypes", [String, Object, Object]),
|
|
203
|
+
__metadata("design:returntype", Promise)
|
|
204
|
+
], BaseAutodiscoverRoute.prototype, "v2", null);
|
|
205
|
+
//# sourceMappingURL=BaseAutodiscoverRoute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseAutodiscoverRoute.js","sourceRoot":"","sources":["../../src/BaseAutodiscoverRoute.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* Autodiscover protocol compatibility: the mechanism a real mail client uses to find this deployment's EAS
|
|
7
|
+
* server URL from just an email address, before it has any credentials or JWT of its own. Exports the
|
|
8
|
+
* backend-agnostic surface - the request/response XML helpers and the abstract route base class. The concrete
|
|
9
|
+
* Mongo/SQL classes a deployment actually instantiates (`AutodiscoverRouteMongo`/`AutodiscoverRouteSQL`) are
|
|
10
|
+
* exported from this package's `./mongo`/`./sql` subpaths instead, alongside every other entity/route/job this
|
|
11
|
+
* library defines - see `src/eas/index.ts`'s identical doc comment for the same convention.
|
|
12
|
+
*
|
|
13
|
+
* A deployment mounts Autodiscover with a trivial subclass supplying its own EAS URL:
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { AutodiscoverRouteMongo } from "@rapidrest/mail/mongo";
|
|
16
|
+
* import { RouteDecorators } from "@rapidrest/service-core";
|
|
17
|
+
* const { Route } = RouteDecorators;
|
|
18
|
+
*
|
|
19
|
+
* @Route("/autodiscover")
|
|
20
|
+
* export class MyAutodiscoverRoute extends AutodiscoverRouteMongo {
|
|
21
|
+
* protected readonly easUrl = "https://mail.example.com/Microsoft-Server-ActiveSync";
|
|
22
|
+
* protected readonly mapiUrl = "https://mail.example.com/mapi/emsmdb";
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export * from "./AutodiscoverXml.js";
|
|
27
|
+
export * from "./BaseAutodiscoverRoute.js";
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAE/E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,cAAc,sBAAsB,CAAC;AACrC,cAAc,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
9
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
import { MailboxMongo } from "@rapidmx/restapi/mongo";
|
|
12
|
+
import { RouteDecorators } from "@rapidrest/service-core";
|
|
13
|
+
import { BaseAutodiscoverRoute } from "../BaseAutodiscoverRoute.js";
|
|
14
|
+
const { Route } = RouteDecorators;
|
|
15
|
+
/**
|
|
16
|
+
* Mongo-backed concrete `BaseAutodiscoverRoute`, mounted at `/autodiscover`. Exported from this plugin's `./mongo`
|
|
17
|
+
* entry point, so the server host mounts it without a wrapper class of its own.
|
|
18
|
+
*
|
|
19
|
+
* @author Jean-Philippe Steinmetz
|
|
20
|
+
*/
|
|
21
|
+
let AutodiscoverRouteMongo = class AutodiscoverRouteMongo extends BaseAutodiscoverRoute {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(...arguments);
|
|
24
|
+
this.mailboxClass = MailboxMongo;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
AutodiscoverRouteMongo = __decorate([
|
|
28
|
+
Route("/autodiscover")
|
|
29
|
+
], AutodiscoverRouteMongo);
|
|
30
|
+
export { AutodiscoverRouteMongo };
|
|
31
|
+
//# sourceMappingURL=AutodiscoverRouteMongo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutodiscoverRouteMongo.js","sourceRoot":"","sources":["../../../src/mongo/AutodiscoverRouteMongo.ts"],"names":[],"mappings":";;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,CAAC;AAElC;;;;;GAKG;AAEI,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,qBAAmC;IAAxE;;QACO,iBAAY,GAAQ,YAAY,CAAC;IAC/C,CAAC;CAAA,CAAA;AAFY,sBAAsB;IADlC,KAAK,CAAC,eAAe,CAAC;GACV,sBAAsB,CAElC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
export * from "./AutodiscoverRouteMongo.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mongo/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,cAAc,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
export * from "./mongo/index.js";
|
|
6
|
+
//# sourceMappingURL=mongo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mongo.js","sourceRoot":"","sources":["../../src/mongo.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
8
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
9
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
10
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
11
|
+
import { Raw } from "typeorm";
|
|
12
|
+
import { MailboxSQL } from "@rapidmx/restapi/sql";
|
|
13
|
+
import { RouteDecorators } from "@rapidrest/service-core";
|
|
14
|
+
import { BaseAutodiscoverRoute } from "../BaseAutodiscoverRoute.js";
|
|
15
|
+
const { Route } = RouteDecorators;
|
|
16
|
+
/**
|
|
17
|
+
* SQL-backed concrete `BaseAutodiscoverRoute`, mounted at `/autodiscover` and exported from this plugin's `./sql`
|
|
18
|
+
* entry point. `aliasQueryValue()` is overridden identically to `MailIngestRouteSQL` - see
|
|
19
|
+
* that class's doc comment for the full vulnerability rationale (unescaped `%`/`_` enabling alias-enumeration
|
|
20
|
+
* and cross-mailbox matches) this same escaping closes here too, since `resolveMailbox()` is reached by
|
|
21
|
+
* unauthenticated callers exactly like `MailIngestRoute.resolve()` is.
|
|
22
|
+
*
|
|
23
|
+
* @author Jean-Philippe Steinmetz
|
|
24
|
+
*/
|
|
25
|
+
let AutodiscoverRouteSQL = class AutodiscoverRouteSQL extends BaseAutodiscoverRoute {
|
|
26
|
+
constructor() {
|
|
27
|
+
super(...arguments);
|
|
28
|
+
this.mailboxClass = MailboxSQL;
|
|
29
|
+
}
|
|
30
|
+
aliasQueryValue(address) {
|
|
31
|
+
const escaped = address.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
32
|
+
return Raw((alias) => `${alias} LIKE :pattern ESCAPE '\\'`, { pattern: `%"${escaped}"%` });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
AutodiscoverRouteSQL = __decorate([
|
|
36
|
+
Route("/autodiscover")
|
|
37
|
+
], AutodiscoverRouteSQL);
|
|
38
|
+
export { AutodiscoverRouteSQL };
|
|
39
|
+
//# sourceMappingURL=AutodiscoverRouteSQL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AutodiscoverRouteSQL.js","sourceRoot":"","sources":["../../../src/sql/AutodiscoverRouteSQL.ts"],"names":[],"mappings":";;;;;;AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,CAAC;AAElC;;;;;;;;GAQG;AAEI,IAAM,oBAAoB,GAA1B,MAAM,oBAAqB,SAAQ,qBAAiC;IAApE;;QACO,iBAAY,GAAQ,UAAU,CAAC;IAM7C,CAAC;IAJa,eAAe,CAAC,OAAe;QACrC,MAAM,OAAO,GAAW,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACtE,OAAO,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,4BAA4B,EAAE,EAAE,OAAO,EAAE,KAAK,OAAO,IAAI,EAAE,CAAC,CAAC;IAC/F,CAAC;CACJ,CAAA;AAPY,oBAAoB;IADhC,KAAK,CAAC,eAAe,CAAC;GACV,oBAAoB,CAOhC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
export * from "./AutodiscoverRouteSQL.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sql/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,cAAc,2BAA2B,CAAC"}
|
package/dist/lib/sql.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
export * from "./sql/index.js";
|
|
6
|
+
//# sourceMappingURL=sql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sql.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts the request's `<EMailAddress>` text content via a small, tightly-scoped regex rather than a DOM
|
|
3
|
+
* parse. Never resolves entities/DTDs - only the literal text between the open/close tags is ever inspected.
|
|
4
|
+
*/
|
|
5
|
+
export declare function extractEmailAddress(xml: string): string | undefined;
|
|
6
|
+
/** Escapes the 5 XML-predefined-entity characters for safe inclusion as element text content. */
|
|
7
|
+
export declare function escapeXml(value: string): string;
|
|
8
|
+
export interface AutodiscoverPoxSuccess {
|
|
9
|
+
emailAddress: string;
|
|
10
|
+
displayName?: string;
|
|
11
|
+
easUrl: string;
|
|
12
|
+
}
|
|
13
|
+
/** The `AcceptableResponseSchema` value a real Outlook desktop client sends to request the Outlook/EXCH
|
|
14
|
+
* response shape (`buildOutlookSuccessXml`) instead of the EAS-only MobileSync one - confirmed against
|
|
15
|
+
* `[MS-OXDSCLI]`'s own Autodiscover Response XSD, whose target namespace is exactly this value. */
|
|
16
|
+
export declare const OUTLOOK_RESPONSE_SCHEMA = "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";
|
|
17
|
+
/**
|
|
18
|
+
* Extracts the request's `<AcceptableResponseSchema>` text content via the same small, tightly-scoped regex
|
|
19
|
+
* approach as `extractEmailAddress` - never a DOM parse, same rationale (unauthenticated, internet-facing
|
|
20
|
+
* endpoint).
|
|
21
|
+
*/
|
|
22
|
+
export declare function extractAcceptableResponseSchema(xml: string): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Builds a real `mobilesync:Response` success document per `[MS-ASCMD]`'s `AutodiscoverMobileSync.xsd` -
|
|
25
|
+
* `Culture`/`User`/`Action.Settings.Server{Type=MobileSync,Url,Name}`, matching the shape confirmed against
|
|
26
|
+
* Microsoft's own "Autodiscover for Exchange ActiveSync developers" example response. A real response can also
|
|
27
|
+
* carry a `CertEnroll` server block for client-certificate enrollment - deliberately omitted, same "pragmatic
|
|
28
|
+
* subset" scoping as Phase 2's own documented gaps, since this library has no certificate-enrollment support.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildPoxSuccessXml({ emailAddress, displayName, easUrl }: AutodiscoverPoxSuccess): string;
|
|
31
|
+
export interface AutodiscoverOutlookSuccess {
|
|
32
|
+
emailAddress: string;
|
|
33
|
+
displayName?: string;
|
|
34
|
+
mapiUrl: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Builds a real Outlook/EXCH `2006a` response per `[MS-OXDSCLI]`'s confirmed Autodiscover Response XSD -
|
|
38
|
+
* an `Autodiscover`(namespace `.../responseschema/2006`) element wrapping a `Response`
|
|
39
|
+
* (namespace `.../outlook/responseschema/2006a`) with `User`/`Account` children.
|
|
40
|
+
*
|
|
41
|
+
* Since this library only speaks MAPI/HTTP (no classic RPC/TCP MAPI transport), the response always
|
|
42
|
+
* advertises exactly one `Protocol`, using `Type`/`Version` as XML ATTRIBUTES rather than the classic
|
|
43
|
+
* `<Type>EXCH</Type>` child element - `[MS-OXDSCLI]`'s "Processing the X-MapiHttpCapability Header" section
|
|
44
|
+
* confirms a mapiHttp-capable client's response "MUST include a Protocol element that contains a Type
|
|
45
|
+
* attribute set to 'mapiHttp' and a Version attribute" and "MUST NOT include a Protocol element that contains
|
|
46
|
+
* a Type element set to 'EXCH' or 'EXPR'" - the two forms are mutually exclusive by design, not merely
|
|
47
|
+
* alternatives. This deployment has no classic RPC/TCP MAPI endpoint to fall back to, so every Outlook-schema
|
|
48
|
+
* request always gets the mapiHttp form - the real `X-MapiHttpCapability` request-header negotiation a full
|
|
49
|
+
* implementation would consult to choose between EXCH/mapiHttp is not implemented, a documented gap.
|
|
50
|
+
* `MailStore.InternalUrl`/`ExternalUrl` are both set to the same `mapiUrl`, since this library serves one URL
|
|
51
|
+
* per deployment with no separate internal/external network split.
|
|
52
|
+
*
|
|
53
|
+
* `LegacyDN`/`DeploymentId` are schema-required fields this library has no real backing value for (no X.500
|
|
54
|
+
* DN resolution, no multi-tenant deployment identity) - both are synthesized placeholders. Real Outlook does
|
|
55
|
+
* not validate their exact content for MAPI/HTTP connectivity; they matter for classic RPC/TCP MAPI
|
|
56
|
+
* free-busy/permissions lookups this library doesn't implement anyway.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildOutlookSuccessXml({ emailAddress, displayName, mapiUrl }: AutodiscoverOutlookSuccess): string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { HttpRequest, HttpResponse } from "@rapidrest/service-core";
|
|
2
|
+
import { Mailbox } from "@rapidmx/restapi";
|
|
3
|
+
/** The plugin packages whose endpoints Autodiscover advertises. */
|
|
4
|
+
export declare const ACTIVESYNC_PLUGIN = "@rapidmx/activesync";
|
|
5
|
+
export declare const MAPI_PLUGIN = "@rapidmx/mapi";
|
|
6
|
+
/**
|
|
7
|
+
* Abstract base for the two Autodiscover endpoints a real mail client uses to find this deployment's EAS
|
|
8
|
+
* server URL from just an email address - classic POX (`POST /autodiscover/autodiscover.xml`, per
|
|
9
|
+
* `[MS-ASCMD]`'s "MobileSync" response schema) and the modern JSON variant Microsoft calls "Autodiscover v2"
|
|
10
|
+
* (`GET /autodiscover/autodiscover.json/v1.0/<email>?Protocol=ActiveSync`). Like `BaseMailIngestRoute`/
|
|
11
|
+
* `BaseEasRoute`, this class is undecorated; its Mongo/SQL concrete classes are mounted at `/autodiscover`, which
|
|
12
|
+
* composes with this class's own relative method paths to land exactly on the real spec's conventional paths (`/autodiscover/autodiscover.xml`, `/autodiscover/autodiscover.json/v1.0/:email`).
|
|
13
|
+
*
|
|
14
|
+
* **Auth: deliberately none.** Real classic Autodiscover conventionally expects the client to send HTTP Basic
|
|
15
|
+
* credentials (email+password), with the server free to answer `401` and force re-entry - a model this
|
|
16
|
+
* library's JWT-only, no-credential-verification-of-our-own boundary (see `BaseEasRoute`'s own doc comment on
|
|
17
|
+
* why per-request Basic Auth was rejected there) can't and shouldn't absorb; no credential-verification
|
|
18
|
+
* function exists in this codebase or its dependencies. Instead, both endpoints here follow Microsoft's own
|
|
19
|
+
* Autodiscover v2 design intent exactly: answer anonymously, and reveal nothing but a deployment-wide,
|
|
20
|
+
* config-supplied EAS server URL - not a per-mailbox secret - once the request email is confirmed to belong to
|
|
21
|
+
* a real `Mailbox` in this deployment. The actual security boundary is unchanged from Phase 2: real mailbox
|
|
22
|
+
* access still requires a JWT at `BaseEasRoute`, exactly as today.
|
|
23
|
+
*
|
|
24
|
+
* **Known gap, deliberately out of scope**: `Action.Redirect` (for multi-tenant hosted providers whose mailbox
|
|
25
|
+
* moved to a different domain) is not implemented - this library serves exactly one EAS URL for its whole
|
|
26
|
+
* deployment, so there's never a different domain to redirect to. The client-side well-known-URL discovery
|
|
27
|
+
* sequence (`MS-OXDISCO`: root domain -> `autodiscover.` subdomain -> unauthenticated HTTP redirect probe ->
|
|
28
|
+
* DNS SRV record -> cache) and the DNS `CNAME`/`SRV` record setup it depends on are also out of scope here -
|
|
29
|
+
* both are client/deployment concerns, not application code; this class only needs to answer correctly once a
|
|
30
|
+
* request actually arrives at one of its two paths.
|
|
31
|
+
*
|
|
32
|
+
* `mailboxClass` is supplied by the Mongo/SQL concrete subclasses following the exact one-line-per-backend
|
|
33
|
+
* pattern used throughout this library. The advertised URLs are built from the `mail:autodiscover:public_url`
|
|
34
|
+
* setting, and each protocol is only advertised while its plugin (`@rapidmx/activesync`, `@rapidmx/mapi`) is
|
|
35
|
+
* loaded, so a client is never pointed at an endpoint this deployment doesn't serve.
|
|
36
|
+
*
|
|
37
|
+
* @author Jean-Philippe Steinmetz
|
|
38
|
+
*/
|
|
39
|
+
export declare abstract class BaseAutodiscoverRoute<M extends Mailbox> {
|
|
40
|
+
protected abstract mailboxClass: any;
|
|
41
|
+
/** The public base URL clients reach this deployment at, e.g. `https://mail.example.com`. */
|
|
42
|
+
protected publicUrl: string;
|
|
43
|
+
/** The EAS endpoint URL to report, or `undefined` when ActiveSync isn't available here. */
|
|
44
|
+
protected get easUrl(): string | undefined;
|
|
45
|
+
/** The MAPI/HTTP `emsmdb` endpoint URL to report to a real Outlook desktop client, or `undefined` when MAPI
|
|
46
|
+
* isn't available here. Only used by `pox()`'s Outlook/EXCH response branch. */
|
|
47
|
+
protected get mapiUrl(): string | undefined;
|
|
48
|
+
private _objectFactory?;
|
|
49
|
+
private mailboxRepo?;
|
|
50
|
+
private logger;
|
|
51
|
+
/**
|
|
52
|
+
* Builds the query value used to match `Mailbox.aliasAddresses` against the given address. See
|
|
53
|
+
* `BaseMailIngestRoute.aliasQueryValue()`'s identical doc comment - same Mongo-array vs.
|
|
54
|
+
* SQL-`simple-json`-column backend split, same override point (`AutodiscoverRouteSQL` overrides this
|
|
55
|
+
* identically to `MailIngestRouteSQL`).
|
|
56
|
+
*/
|
|
57
|
+
protected aliasQueryValue(address: string): any;
|
|
58
|
+
private endpointUrl;
|
|
59
|
+
init(): Promise<void>;
|
|
60
|
+
private resolveMailbox;
|
|
61
|
+
/**
|
|
62
|
+
* Classic POX Autodiscover. Per Microsoft's own client-behavior documentation, a permanent failure is just
|
|
63
|
+
* as validly conveyed via a plain HTTP status as via an inner `Error` element, so this uses the HTTP-status
|
|
64
|
+
* form for both error cases rather than inventing values for `[MS-ASCMD]`'s provider-specific numeric
|
|
65
|
+
* error-code table.
|
|
66
|
+
*
|
|
67
|
+
* Branches on the request's `AcceptableResponseSchema` to decide which response shape to build: a real
|
|
68
|
+
* Outlook desktop client sends `[MS-OXDSCLI]`'s Outlook/EXCH schema (`OUTLOOK_RESPONSE_SCHEMA`) to locate
|
|
69
|
+
* this deployment's MAPI/HTTP endpoint (`buildOutlookSuccessXml`); any other value (or a mobile/EAS-only
|
|
70
|
+
* client that omits the field entirely) gets the original MobileSync/EAS response (`buildPoxSuccessXml`).
|
|
71
|
+
*/
|
|
72
|
+
pox(req: HttpRequest, res: HttpResponse): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Autodiscover v2 (JSON). Only the `ActiveSync` protocol is served - this library has no EWS/other
|
|
75
|
+
* protocol surface to advertise - so any other `Protocol` value is rejected outright rather than silently
|
|
76
|
+
* answered with an EAS URL under the wrong protocol name.
|
|
77
|
+
*/
|
|
78
|
+
v2(email: string, protocol: string | undefined, res: HttpResponse): Promise<void>;
|
|
79
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autodiscover protocol compatibility: the mechanism a real mail client uses to find this deployment's EAS
|
|
3
|
+
* server URL from just an email address, before it has any credentials or JWT of its own. Exports the
|
|
4
|
+
* backend-agnostic surface - the request/response XML helpers and the abstract route base class. The concrete
|
|
5
|
+
* Mongo/SQL classes a deployment actually instantiates (`AutodiscoverRouteMongo`/`AutodiscoverRouteSQL`) are
|
|
6
|
+
* exported from this package's `./mongo`/`./sql` subpaths instead, alongside every other entity/route/job this
|
|
7
|
+
* library defines - see `src/eas/index.ts`'s identical doc comment for the same convention.
|
|
8
|
+
*
|
|
9
|
+
* A deployment mounts Autodiscover with a trivial subclass supplying its own EAS URL:
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { AutodiscoverRouteMongo } from "@rapidrest/mail/mongo";
|
|
12
|
+
* import { RouteDecorators } from "@rapidrest/service-core";
|
|
13
|
+
* const { Route } = RouteDecorators;
|
|
14
|
+
*
|
|
15
|
+
* @Route("/autodiscover")
|
|
16
|
+
* export class MyAutodiscoverRoute extends AutodiscoverRouteMongo {
|
|
17
|
+
* protected readonly easUrl = "https://mail.example.com/Microsoft-Server-ActiveSync";
|
|
18
|
+
* protected readonly mapiUrl = "https://mail.example.com/mapi/emsmdb";
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export * from "./AutodiscoverXml.js";
|
|
23
|
+
export * from "./BaseAutodiscoverRoute.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MailboxMongo } from "@rapidmx/restapi/mongo";
|
|
2
|
+
import { BaseAutodiscoverRoute } from "../BaseAutodiscoverRoute.js";
|
|
3
|
+
/**
|
|
4
|
+
* Mongo-backed concrete `BaseAutodiscoverRoute`, mounted at `/autodiscover`. Exported from this plugin's `./mongo`
|
|
5
|
+
* entry point, so the server host mounts it without a wrapper class of its own.
|
|
6
|
+
*
|
|
7
|
+
* @author Jean-Philippe Steinmetz
|
|
8
|
+
*/
|
|
9
|
+
export declare class AutodiscoverRouteMongo extends BaseAutodiscoverRoute<MailboxMongo> {
|
|
10
|
+
protected mailboxClass: any;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./AutodiscoverRouteMongo.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./mongo/index.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MailboxSQL } from "@rapidmx/restapi/sql";
|
|
2
|
+
import { BaseAutodiscoverRoute } from "../BaseAutodiscoverRoute.js";
|
|
3
|
+
/**
|
|
4
|
+
* SQL-backed concrete `BaseAutodiscoverRoute`, mounted at `/autodiscover` and exported from this plugin's `./sql`
|
|
5
|
+
* entry point. `aliasQueryValue()` is overridden identically to `MailIngestRouteSQL` - see
|
|
6
|
+
* that class's doc comment for the full vulnerability rationale (unescaped `%`/`_` enabling alias-enumeration
|
|
7
|
+
* and cross-mailbox matches) this same escaping closes here too, since `resolveMailbox()` is reached by
|
|
8
|
+
* unauthenticated callers exactly like `MailIngestRoute.resolve()` is.
|
|
9
|
+
*
|
|
10
|
+
* @author Jean-Philippe Steinmetz
|
|
11
|
+
*/
|
|
12
|
+
export declare class AutodiscoverRouteSQL extends BaseAutodiscoverRoute<MailboxSQL> {
|
|
13
|
+
protected mailboxClass: any;
|
|
14
|
+
protected aliasQueryValue(address: string): any;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./AutodiscoverRouteSQL.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./sql/index.js";
|