@ttoss/http-server-auth 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -125,6 +125,51 @@ type AuthenticatedUser = {
125
125
 
126
126
  Both `apiToken.lookup` and `jwt.mapPayload` receive the Koa `ctx` as a second argument, enabling request-scoped work (e.g. reading a per-request connection off `ctx.db` or updating `lastUsedAt`). The single-argument signatures keep working — `ctx` is purely additive.
127
127
 
128
+ ## Email and password flows
129
+
130
+ `emailAuth()` mounts the credential flows — password sign-up and sign-in, magic
131
+ links, mailed numeric codes, address confirmation and password reset — as a Koa
132
+ `Router`. It is a thin adapter over `createEmailAuthHandlers` from
133
+ [`@ttoss/auth-core`](https://ttoss.dev/docs/modules/packages/auth-core), which
134
+ owns the mechanics; this package only maps `ctx` to and from it.
135
+
136
+ `modes` decides which routes exist, so an app that signs users in with a mailed
137
+ code alone never exposes a password endpoint.
138
+
139
+ ```ts
140
+ import { App, bodyParser } from '@ttoss/http-server';
141
+ import { authMiddleware, emailAuth } from '@ttoss/http-server-auth';
142
+
143
+ const app = new App();
144
+ app.use(bodyParser());
145
+
146
+ app.use(
147
+ emailAuth({
148
+ modes: ['emailCode'],
149
+ prefix: '/v1',
150
+ userStore,
151
+ oneTimeTokenStore,
152
+ issueSession: (user) => issueSession(user),
153
+ sendEmail: async ({ to, token }) => {
154
+ await ses.send(buildCodeEmail({ to, code: token }));
155
+ },
156
+ }).routes()
157
+ );
158
+
159
+ // Mount after, so the sign-in routes stay reachable without a token.
160
+ app.use(authMiddleware({ strategies: ['jwt'], jwt: { secret } }));
161
+ ```
162
+
163
+ Every route is a `POST`, link redemptions included: the token arrives in the body
164
+ from the page the user landed on, which keeps it out of server logs and out of
165
+ the `Referer` header. The engine also reads a token from the query string, so a
166
+ `GET` redemption is available to an app that wants one.
167
+
168
+ Mount it before `authMiddleware` (or exempt its paths) — a client calls these
169
+ routes precisely because it has no token yet. `prefix` applies to every mounted
170
+ path when the app versions its API; individual paths are overridable through
171
+ `paths`.
172
+
128
173
  ## OAuth authorization server
129
174
 
130
175
  The package also ships `oauthServer()` — a Koa `Router` that issues tokens (`/authorize`, `/token`, `/register`, discovery), a thin adapter over `createOAuthHandlers` from [`@ttoss/auth-core`](https://ttoss.dev/docs/modules/packages/auth-core). Pair it with the `oauth` verification strategy above when one deployment both issues and verifies. See the [OAuth Authorization Server](https://ttoss.dev/docs/engineering/guidelines/oauth-authorization-server) guideline.
package/dist/index.cjs CHANGED
@@ -175,12 +175,17 @@ var authMiddleware = options => {
175
175
  };
176
176
 
177
177
  //#endregion
178
- //#region src/oauthServer.ts
178
+ //#region src/koaAdapter.ts
179
+ /**
180
+ * Translation between Koa's `ctx` and the framework-agnostic request/response
181
+ * shapes the `@ttoss/auth-core` engines speak. Shared by every adapter in this
182
+ * package so the mapping exists once.
183
+ */
179
184
  /** Normalizes a Koa query/header value (which may be an array) to a string. */
180
185
  var firstValue = value => {
181
186
  return Array.isArray(value) ? value[0] : value;
182
187
  };
183
- var toOAuthRequest = ctx => {
188
+ var toAuthRequest = ctx => {
184
189
  const query = {};
185
190
  for (const [key, value] of Object.entries(ctx.query)) query[key] = firstValue(value);
186
191
  const headers = {};
@@ -191,6 +196,10 @@ var toOAuthRequest = ctx => {
191
196
  headers
192
197
  };
193
198
  };
199
+ /**
200
+ * Applies an engine response to `ctx`. A response carries either a JSON body
201
+ * with a status or a redirect, never both.
202
+ */
194
203
  var applyResponse = (ctx, res) => {
195
204
  if (res.redirect !== void 0) {
196
205
  ctx.redirect(res.redirect);
@@ -199,6 +208,67 @@ var applyResponse = (ctx, res) => {
199
208
  ctx.status = res.status;
200
209
  ctx.body = res.body;
201
210
  };
211
+
212
+ //#endregion
213
+ //#region src/emailAuth.ts
214
+ /**
215
+ * Mounts the email and password credential flows as a Koa `Router`, adapting
216
+ * the runner-agnostic engine from `@ttoss/auth-core`. Which routes appear is
217
+ * decided by `modes`, so an app that only signs users in with a mailed code
218
+ * never exposes a password endpoint.
219
+ *
220
+ * Every route is a `POST`, including the link redemptions: the token arrives in
221
+ * the body from the page the user landed on, which keeps it out of server logs
222
+ * and out of the `Referer` header. The engine also reads a token from the query
223
+ * string, so a `GET` redemption can be added by an application that wants one.
224
+ *
225
+ * Mount it **before** `authMiddleware`, or exempt its paths — the sign-in
226
+ * routes are what a client calls precisely because it has no token yet.
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * import { App, bodyParser } from '@ttoss/http-server';
231
+ * import { emailAuth } from '@ttoss/http-server-auth';
232
+ *
233
+ * const app = new App();
234
+ * app.use(bodyParser());
235
+ * app.use(
236
+ * emailAuth({
237
+ * modes: ['emailCode'],
238
+ * userStore,
239
+ * oneTimeTokenStore,
240
+ * issueSession: (user) => issueSession(user),
241
+ * sendEmail: async ({ to, token }) => {
242
+ * await ses.send(buildCodeEmail({ to, code: token }));
243
+ * },
244
+ * }).routes()
245
+ * );
246
+ * ```
247
+ */
248
+ var emailAuth = options => {
249
+ const handlers = (0, _ttoss_auth_core.createEmailAuthHandlers)(options);
250
+ const router = new _ttoss_http_server.Router();
251
+ const prefix = options.prefix ?? "";
252
+ const mount = (path, handler) => {
253
+ if (!handler) return;
254
+ router.post(`${prefix}${path}`, async ctx => {
255
+ applyResponse(ctx, await handler(toAuthRequest(ctx)));
256
+ });
257
+ };
258
+ mount(handlers.paths.signUp, handlers.signUp);
259
+ mount(handlers.paths.signIn, handlers.signIn);
260
+ mount(handlers.paths.sendMagicLink, handlers.sendMagicLink);
261
+ mount(handlers.paths.verifyMagicLink, handlers.verifyMagicLink);
262
+ mount(handlers.paths.sendEmailCode, handlers.sendEmailCode);
263
+ mount(handlers.paths.verifyEmailCode, handlers.verifyEmailCode);
264
+ mount(handlers.paths.verifyEmail, handlers.verifyEmail);
265
+ mount(handlers.paths.requestPasswordReset, handlers.requestPasswordReset);
266
+ mount(handlers.paths.resetPassword, handlers.resetPassword);
267
+ return router;
268
+ };
269
+
270
+ //#endregion
271
+ //#region src/oauthServer.ts
202
272
  /**
203
273
  * Mounts an OAuth 2.1 Authorization Server (issuing tokens) as a Koa `Router`,
204
274
  * adapting the runner-agnostic engine from `@ttoss/auth-core`. Exposes the
@@ -229,13 +299,13 @@ var oauthServer = options => {
229
299
  applyResponse(ctx, prm);
230
300
  });
231
301
  router.get(engine.paths.authorize, async ctx => {
232
- applyResponse(ctx, await engine.authorize(toOAuthRequest(ctx)));
302
+ applyResponse(ctx, await engine.authorize(toAuthRequest(ctx)));
233
303
  });
234
304
  router.post(engine.paths.token, async ctx => {
235
- applyResponse(ctx, await engine.token(toOAuthRequest(ctx)));
305
+ applyResponse(ctx, await engine.token(toAuthRequest(ctx)));
236
306
  });
237
307
  router.post(engine.paths.register, async ctx => {
238
- applyResponse(ctx, await engine.register(toOAuthRequest(ctx)));
308
+ applyResponse(ctx, await engine.register(toAuthRequest(ctx)));
239
309
  });
240
310
  return router;
241
311
  };
@@ -275,6 +345,12 @@ var requireAuth = options => {
275
345
 
276
346
  //#endregion
277
347
  exports.authMiddleware = authMiddleware;
348
+ Object.defineProperty(exports, 'createEmailAuthHandlers', {
349
+ enumerable: true,
350
+ get: function () {
351
+ return _ttoss_auth_core.createEmailAuthHandlers;
352
+ }
353
+ });
278
354
  Object.defineProperty(exports, 'createOAuthHandlers', {
279
355
  enumerable: true,
280
356
  get: function () {
@@ -282,6 +358,13 @@ Object.defineProperty(exports, 'createOAuthHandlers', {
282
358
  }
283
359
  });
284
360
  exports.createProtectedResourceMetadataMiddleware = createProtectedResourceMetadataMiddleware;
361
+ exports.emailAuth = emailAuth;
362
+ Object.defineProperty(exports, 'emailAuthErrorCodes', {
363
+ enumerable: true,
364
+ get: function () {
365
+ return _ttoss_auth_core.emailAuthErrorCodes;
366
+ }
367
+ });
285
368
  Object.defineProperty(exports, 'getWwwAuthenticateHeader', {
286
369
  enumerable: true,
287
370
  get: function () {
@@ -289,5 +372,11 @@ Object.defineProperty(exports, 'getWwwAuthenticateHeader', {
289
372
  }
290
373
  });
291
374
  exports.isOriginAllowed = isOriginAllowed;
375
+ Object.defineProperty(exports, 'normalizeEmail', {
376
+ enumerable: true,
377
+ get: function () {
378
+ return _ttoss_auth_core.normalizeEmail;
379
+ }
380
+ });
292
381
  exports.oauthServer = oauthServer;
293
382
  exports.requireAuth = requireAuth;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { Context, Middleware, Next, Router } from "@ttoss/http-server";
3
- import { AuthCodeStore, AuthorizeRequest, ClientStore, IssueTokensArgs, IssuedTokens, OAuthClient, OAuthClientMetadata, OAuthHandlers, OAuthServerOptions, OAuthServerOptions as OAuthServerOptions$1, OnAuthorizeArgs, OnAuthorizeResult, OnRefreshTokenArgs, OnRefreshTokenResult, StoredAuthorizationCode, createOAuthHandlers, getWwwAuthenticateHeader } from "@ttoss/auth-core";
3
+ import { AuthCodeStore, AuthorizeRequest, ClientStore, EmailAuthDelivery, EmailAuthErrorCode, EmailAuthHandler, EmailAuthHandlers, EmailAuthHooks, EmailAuthMode, EmailAuthOptions, EmailAuthOptions as EmailAuthOptions$1, EmailAuthPaths, EmailAuthSession, EmailAuthTtl, EmailAuthUser, EmailAuthUserStore, EmailCodeOptions, IssueTokensArgs, IssuedTokens, OAuthClient, OAuthClientMetadata, OAuthHandlers, OAuthServerOptions, OAuthServerOptions as OAuthServerOptions$1, OnAuthorizeArgs, OnAuthorizeResult, OnRefreshTokenArgs, OnRefreshTokenResult, OneTimeTokenPurpose, OneTimeTokenStore, PasswordOptions, StoredAuthorizationCode, StoredOneTimeToken, createEmailAuthHandlers, createOAuthHandlers, emailAuthErrorCodes, getWwwAuthenticateHeader, normalizeEmail } from "@ttoss/auth-core";
4
4
 
5
5
  //#region src/types.d.ts
6
6
  type AuthenticatedUser = {
@@ -93,6 +93,49 @@ type AuthMiddlewareOptions = {
93
93
  */
94
94
  declare const authMiddleware: (options: AuthMiddlewareOptions) => (ctx: Context, next: Next) => Promise<void>;
95
95
  //#endregion
96
+ //#region src/emailAuth.d.ts
97
+ /**
98
+ * Mounts the email and password credential flows as a Koa `Router`, adapting
99
+ * the runner-agnostic engine from `@ttoss/auth-core`. Which routes appear is
100
+ * decided by `modes`, so an app that only signs users in with a mailed code
101
+ * never exposes a password endpoint.
102
+ *
103
+ * Every route is a `POST`, including the link redemptions: the token arrives in
104
+ * the body from the page the user landed on, which keeps it out of server logs
105
+ * and out of the `Referer` header. The engine also reads a token from the query
106
+ * string, so a `GET` redemption can be added by an application that wants one.
107
+ *
108
+ * Mount it **before** `authMiddleware`, or exempt its paths — the sign-in
109
+ * routes are what a client calls precisely because it has no token yet.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * import { App, bodyParser } from '@ttoss/http-server';
114
+ * import { emailAuth } from '@ttoss/http-server-auth';
115
+ *
116
+ * const app = new App();
117
+ * app.use(bodyParser());
118
+ * app.use(
119
+ * emailAuth({
120
+ * modes: ['emailCode'],
121
+ * userStore,
122
+ * oneTimeTokenStore,
123
+ * issueSession: (user) => issueSession(user),
124
+ * sendEmail: async ({ to, token }) => {
125
+ * await ses.send(buildCodeEmail({ to, code: token }));
126
+ * },
127
+ * }).routes()
128
+ * );
129
+ * ```
130
+ */
131
+ declare const emailAuth: (options: EmailAuthOptions$1 & {
132
+ /**
133
+ * Prefix applied to every mounted path, e.g. `/v1`. Useful when the app
134
+ * versions its API and the engine's default paths are otherwise fine.
135
+ */
136
+ prefix?: string;
137
+ }) => Router;
138
+ //#endregion
96
139
  //#region src/oauthServer.d.ts
97
140
  /**
98
141
  * Mounts an OAuth 2.1 Authorization Server (issuing tokens) as a Koa `Router`,
@@ -141,4 +184,4 @@ declare const isOriginAllowed: (origin: string, allowedOrigins: Array<string | R
141
184
  */
142
185
  declare const requireAuth: (options: AuthMiddlewareOptions) => ((ctx: Context, next: Next) => Promise<void>);
143
186
  //#endregion
144
- export { type ApiTokenOptions, type AuthCodeStore, type AuthMiddlewareOptions, type AuthStrategy, type AuthenticatedUser, type AuthorizeRequest, type ClientStore, type IssueTokensArgs, type IssuedTokens, type JwtOptions, type OAuthClient, type OAuthClientMetadata, type OAuthHandlers, type OAuthOptions, type OAuthServerOptions, type OnAuthorizeArgs, type OnAuthorizeResult, type OnRefreshTokenArgs, type OnRefreshTokenResult, type StoredAuthorizationCode, type SystemOptions, authMiddleware, createOAuthHandlers, createProtectedResourceMetadataMiddleware, getWwwAuthenticateHeader, isOriginAllowed, oauthServer, requireAuth };
187
+ export { type ApiTokenOptions, type AuthCodeStore, type AuthMiddlewareOptions, type AuthStrategy, type AuthenticatedUser, type AuthorizeRequest, type ClientStore, type EmailAuthDelivery, type EmailAuthErrorCode, type EmailAuthHandler, type EmailAuthHandlers, type EmailAuthHooks, type EmailAuthMode, type EmailAuthOptions, type EmailAuthPaths, type EmailAuthSession, type EmailAuthTtl, type EmailAuthUser, type EmailAuthUserStore, type EmailCodeOptions, type IssueTokensArgs, type IssuedTokens, type JwtOptions, type OAuthClient, type OAuthClientMetadata, type OAuthHandlers, type OAuthOptions, type OAuthServerOptions, type OnAuthorizeArgs, type OnAuthorizeResult, type OnRefreshTokenArgs, type OnRefreshTokenResult, type OneTimeTokenPurpose, type OneTimeTokenStore, type PasswordOptions, type StoredAuthorizationCode, type StoredOneTimeToken, type SystemOptions, authMiddleware, createEmailAuthHandlers, createOAuthHandlers, createProtectedResourceMetadataMiddleware, emailAuth, emailAuthErrorCodes, getWwwAuthenticateHeader, isOriginAllowed, normalizeEmail, oauthServer, requireAuth };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { AuthCodeStore, AuthorizeRequest, ClientStore, IssueTokensArgs, IssuedTokens, OAuthClient, OAuthClientMetadata, OAuthHandlers, OAuthServerOptions, OAuthServerOptions as OAuthServerOptions$1, OnAuthorizeArgs, OnAuthorizeResult, OnRefreshTokenArgs, OnRefreshTokenResult, StoredAuthorizationCode, createOAuthHandlers, getWwwAuthenticateHeader } from "@ttoss/auth-core";
2
+ import { AuthCodeStore, AuthorizeRequest, ClientStore, EmailAuthDelivery, EmailAuthErrorCode, EmailAuthHandler, EmailAuthHandlers, EmailAuthHooks, EmailAuthMode, EmailAuthOptions, EmailAuthOptions as EmailAuthOptions$1, EmailAuthPaths, EmailAuthSession, EmailAuthTtl, EmailAuthUser, EmailAuthUserStore, EmailCodeOptions, IssueTokensArgs, IssuedTokens, OAuthClient, OAuthClientMetadata, OAuthHandlers, OAuthServerOptions, OAuthServerOptions as OAuthServerOptions$1, OnAuthorizeArgs, OnAuthorizeResult, OnRefreshTokenArgs, OnRefreshTokenResult, OneTimeTokenPurpose, OneTimeTokenStore, PasswordOptions, StoredAuthorizationCode, StoredOneTimeToken, createEmailAuthHandlers, createOAuthHandlers, emailAuthErrorCodes, getWwwAuthenticateHeader, normalizeEmail } from "@ttoss/auth-core";
3
3
  import { Context, Middleware, Next, Router } from "@ttoss/http-server";
4
4
 
5
5
  //#region src/types.d.ts
@@ -93,6 +93,49 @@ type AuthMiddlewareOptions = {
93
93
  */
94
94
  declare const authMiddleware: (options: AuthMiddlewareOptions) => (ctx: Context, next: Next) => Promise<void>;
95
95
  //#endregion
96
+ //#region src/emailAuth.d.ts
97
+ /**
98
+ * Mounts the email and password credential flows as a Koa `Router`, adapting
99
+ * the runner-agnostic engine from `@ttoss/auth-core`. Which routes appear is
100
+ * decided by `modes`, so an app that only signs users in with a mailed code
101
+ * never exposes a password endpoint.
102
+ *
103
+ * Every route is a `POST`, including the link redemptions: the token arrives in
104
+ * the body from the page the user landed on, which keeps it out of server logs
105
+ * and out of the `Referer` header. The engine also reads a token from the query
106
+ * string, so a `GET` redemption can be added by an application that wants one.
107
+ *
108
+ * Mount it **before** `authMiddleware`, or exempt its paths — the sign-in
109
+ * routes are what a client calls precisely because it has no token yet.
110
+ *
111
+ * @example
112
+ * ```typescript
113
+ * import { App, bodyParser } from '@ttoss/http-server';
114
+ * import { emailAuth } from '@ttoss/http-server-auth';
115
+ *
116
+ * const app = new App();
117
+ * app.use(bodyParser());
118
+ * app.use(
119
+ * emailAuth({
120
+ * modes: ['emailCode'],
121
+ * userStore,
122
+ * oneTimeTokenStore,
123
+ * issueSession: (user) => issueSession(user),
124
+ * sendEmail: async ({ to, token }) => {
125
+ * await ses.send(buildCodeEmail({ to, code: token }));
126
+ * },
127
+ * }).routes()
128
+ * );
129
+ * ```
130
+ */
131
+ declare const emailAuth: (options: EmailAuthOptions$1 & {
132
+ /**
133
+ * Prefix applied to every mounted path, e.g. `/v1`. Useful when the app
134
+ * versions its API and the engine's default paths are otherwise fine.
135
+ */
136
+ prefix?: string;
137
+ }) => Router;
138
+ //#endregion
96
139
  //#region src/oauthServer.d.ts
97
140
  /**
98
141
  * Mounts an OAuth 2.1 Authorization Server (issuing tokens) as a Koa `Router`,
@@ -141,4 +184,4 @@ declare const isOriginAllowed: (origin: string, allowedOrigins: Array<string | R
141
184
  */
142
185
  declare const requireAuth: (options: AuthMiddlewareOptions) => ((ctx: Context, next: Next) => Promise<void>);
143
186
  //#endregion
144
- export { type ApiTokenOptions, type AuthCodeStore, type AuthMiddlewareOptions, type AuthStrategy, type AuthenticatedUser, type AuthorizeRequest, type ClientStore, type IssueTokensArgs, type IssuedTokens, type JwtOptions, type OAuthClient, type OAuthClientMetadata, type OAuthHandlers, type OAuthOptions, type OAuthServerOptions, type OnAuthorizeArgs, type OnAuthorizeResult, type OnRefreshTokenArgs, type OnRefreshTokenResult, type StoredAuthorizationCode, type SystemOptions, authMiddleware, createOAuthHandlers, createProtectedResourceMetadataMiddleware, getWwwAuthenticateHeader, isOriginAllowed, oauthServer, requireAuth };
187
+ export { type ApiTokenOptions, type AuthCodeStore, type AuthMiddlewareOptions, type AuthStrategy, type AuthenticatedUser, type AuthorizeRequest, type ClientStore, type EmailAuthDelivery, type EmailAuthErrorCode, type EmailAuthHandler, type EmailAuthHandlers, type EmailAuthHooks, type EmailAuthMode, type EmailAuthOptions, type EmailAuthPaths, type EmailAuthSession, type EmailAuthTtl, type EmailAuthUser, type EmailAuthUserStore, type EmailCodeOptions, type IssueTokensArgs, type IssuedTokens, type JwtOptions, type OAuthClient, type OAuthClientMetadata, type OAuthHandlers, type OAuthOptions, type OAuthServerOptions, type OnAuthorizeArgs, type OnAuthorizeResult, type OnRefreshTokenArgs, type OnRefreshTokenResult, type OneTimeTokenPurpose, type OneTimeTokenStore, type PasswordOptions, type StoredAuthorizationCode, type StoredOneTimeToken, type SystemOptions, authMiddleware, createEmailAuthHandlers, createOAuthHandlers, createProtectedResourceMetadataMiddleware, emailAuth, emailAuthErrorCodes, getWwwAuthenticateHeader, isOriginAllowed, normalizeEmail, oauthServer, requireAuth };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
2
2
  import crypto from "node:crypto";
3
- import { createOAuthHandlers, createOAuthHandlers as createOAuthHandlers$1, getWwwAuthenticateHeader, hashApiToken, verifyJwt } from "@ttoss/auth-core";
3
+ import { createEmailAuthHandlers, createEmailAuthHandlers as createEmailAuthHandlers$1, createOAuthHandlers, createOAuthHandlers as createOAuthHandlers$1, emailAuthErrorCodes, getWwwAuthenticateHeader, hashApiToken, normalizeEmail, verifyJwt } from "@ttoss/auth-core";
4
4
  import { Router } from "@ttoss/http-server";
5
5
 
6
6
  //#region src/origin.ts
@@ -144,12 +144,17 @@ var authMiddleware = options => {
144
144
  };
145
145
 
146
146
  //#endregion
147
- //#region src/oauthServer.ts
147
+ //#region src/koaAdapter.ts
148
+ /**
149
+ * Translation between Koa's `ctx` and the framework-agnostic request/response
150
+ * shapes the `@ttoss/auth-core` engines speak. Shared by every adapter in this
151
+ * package so the mapping exists once.
152
+ */
148
153
  /** Normalizes a Koa query/header value (which may be an array) to a string. */
149
154
  var firstValue = value => {
150
155
  return Array.isArray(value) ? value[0] : value;
151
156
  };
152
- var toOAuthRequest = ctx => {
157
+ var toAuthRequest = ctx => {
153
158
  const query = {};
154
159
  for (const [key, value] of Object.entries(ctx.query)) query[key] = firstValue(value);
155
160
  const headers = {};
@@ -160,6 +165,10 @@ var toOAuthRequest = ctx => {
160
165
  headers
161
166
  };
162
167
  };
168
+ /**
169
+ * Applies an engine response to `ctx`. A response carries either a JSON body
170
+ * with a status or a redirect, never both.
171
+ */
163
172
  var applyResponse = (ctx, res) => {
164
173
  if (res.redirect !== void 0) {
165
174
  ctx.redirect(res.redirect);
@@ -168,6 +177,67 @@ var applyResponse = (ctx, res) => {
168
177
  ctx.status = res.status;
169
178
  ctx.body = res.body;
170
179
  };
180
+
181
+ //#endregion
182
+ //#region src/emailAuth.ts
183
+ /**
184
+ * Mounts the email and password credential flows as a Koa `Router`, adapting
185
+ * the runner-agnostic engine from `@ttoss/auth-core`. Which routes appear is
186
+ * decided by `modes`, so an app that only signs users in with a mailed code
187
+ * never exposes a password endpoint.
188
+ *
189
+ * Every route is a `POST`, including the link redemptions: the token arrives in
190
+ * the body from the page the user landed on, which keeps it out of server logs
191
+ * and out of the `Referer` header. The engine also reads a token from the query
192
+ * string, so a `GET` redemption can be added by an application that wants one.
193
+ *
194
+ * Mount it **before** `authMiddleware`, or exempt its paths — the sign-in
195
+ * routes are what a client calls precisely because it has no token yet.
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * import { App, bodyParser } from '@ttoss/http-server';
200
+ * import { emailAuth } from '@ttoss/http-server-auth';
201
+ *
202
+ * const app = new App();
203
+ * app.use(bodyParser());
204
+ * app.use(
205
+ * emailAuth({
206
+ * modes: ['emailCode'],
207
+ * userStore,
208
+ * oneTimeTokenStore,
209
+ * issueSession: (user) => issueSession(user),
210
+ * sendEmail: async ({ to, token }) => {
211
+ * await ses.send(buildCodeEmail({ to, code: token }));
212
+ * },
213
+ * }).routes()
214
+ * );
215
+ * ```
216
+ */
217
+ var emailAuth = options => {
218
+ const handlers = createEmailAuthHandlers$1(options);
219
+ const router = new Router();
220
+ const prefix = options.prefix ?? "";
221
+ const mount = (path, handler) => {
222
+ if (!handler) return;
223
+ router.post(`${prefix}${path}`, async ctx => {
224
+ applyResponse(ctx, await handler(toAuthRequest(ctx)));
225
+ });
226
+ };
227
+ mount(handlers.paths.signUp, handlers.signUp);
228
+ mount(handlers.paths.signIn, handlers.signIn);
229
+ mount(handlers.paths.sendMagicLink, handlers.sendMagicLink);
230
+ mount(handlers.paths.verifyMagicLink, handlers.verifyMagicLink);
231
+ mount(handlers.paths.sendEmailCode, handlers.sendEmailCode);
232
+ mount(handlers.paths.verifyEmailCode, handlers.verifyEmailCode);
233
+ mount(handlers.paths.verifyEmail, handlers.verifyEmail);
234
+ mount(handlers.paths.requestPasswordReset, handlers.requestPasswordReset);
235
+ mount(handlers.paths.resetPassword, handlers.resetPassword);
236
+ return router;
237
+ };
238
+
239
+ //#endregion
240
+ //#region src/oauthServer.ts
171
241
  /**
172
242
  * Mounts an OAuth 2.1 Authorization Server (issuing tokens) as a Koa `Router`,
173
243
  * adapting the runner-agnostic engine from `@ttoss/auth-core`. Exposes the
@@ -198,13 +268,13 @@ var oauthServer = options => {
198
268
  applyResponse(ctx, prm);
199
269
  });
200
270
  router.get(engine.paths.authorize, async ctx => {
201
- applyResponse(ctx, await engine.authorize(toOAuthRequest(ctx)));
271
+ applyResponse(ctx, await engine.authorize(toAuthRequest(ctx)));
202
272
  });
203
273
  router.post(engine.paths.token, async ctx => {
204
- applyResponse(ctx, await engine.token(toOAuthRequest(ctx)));
274
+ applyResponse(ctx, await engine.token(toAuthRequest(ctx)));
205
275
  });
206
276
  router.post(engine.paths.register, async ctx => {
207
- applyResponse(ctx, await engine.register(toOAuthRequest(ctx)));
277
+ applyResponse(ctx, await engine.register(toAuthRequest(ctx)));
208
278
  });
209
279
  return router;
210
280
  };
@@ -243,4 +313,4 @@ var requireAuth = options => {
243
313
  };
244
314
 
245
315
  //#endregion
246
- export { authMiddleware, createOAuthHandlers, createProtectedResourceMetadataMiddleware, getWwwAuthenticateHeader, isOriginAllowed, oauthServer, requireAuth };
316
+ export { authMiddleware, createEmailAuthHandlers, createOAuthHandlers, createProtectedResourceMetadataMiddleware, emailAuth, emailAuthErrorCodes, getWwwAuthenticateHeader, isOriginAllowed, normalizeEmail, oauthServer, requireAuth };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-auth",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Authentication middleware for @ttoss/http-server",
5
5
  "keywords": [
6
6
  "auth",
@@ -32,15 +32,15 @@
32
32
  "dist"
33
33
  ],
34
34
  "dependencies": {
35
- "@ttoss/auth-core": "^0.11.0"
35
+ "@ttoss/auth-core": "^0.12.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/koa": "^3.0.3",
39
39
  "jest": "^30.4.2",
40
40
  "supertest": "^7.2.2",
41
41
  "tsdown": "^0.22.2",
42
- "@ttoss/config": "^1.37.17",
43
- "@ttoss/http-server": "^0.7.1"
42
+ "@ttoss/http-server": "^0.7.1",
43
+ "@ttoss/config": "^1.37.17"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@ttoss/http-server": "^0.7.1"