Skip to content

Commit 95693a9

Browse files
committed
feat: remove AuthStep so that flow steps can be endpoint derivatives
1 parent 41ec7bd commit 95693a9

8 files changed

Lines changed: 36 additions & 47 deletions

File tree

docs/authentication.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ class MainRouter extends BaseApiRouter {
268268
- Session-based systems where session state must be maintained
269269

270270
```typescript
271-
import { SessionAuthenticationScheme, AuthFlow, AuthStep } from 'api-machine';
271+
import { SessionAuthenticationScheme, AuthFlow, BaseApiEndpoint } from 'api-machine';
272272

273273
class OAuth2Scheme extends SessionAuthenticationScheme {
274274
getSecurityScheme() {
@@ -287,7 +287,7 @@ class OAuth2Scheme extends SessionAuthenticationScheme {
287287
getAuthFlow(): AuthFlow {
288288
return {
289289
// Challenge step
290-
challenge: class extends AuthStep {
290+
challenge: class extends BaseApiEndpoint {
291291
override path = '/challenge';
292292
override description = 'Generate authorization challenge (PKCE)';
293293

@@ -297,7 +297,7 @@ class OAuth2Scheme extends SessionAuthenticationScheme {
297297
}
298298
},
299299
// Authorization step
300-
authorization: class extends AuthStep {
300+
authorization: class extends BaseApiEndpoint {
301301
override path = '/authorize';
302302
override description = 'User authorizes application at OAuth2 provider';
303303

@@ -308,7 +308,7 @@ class OAuth2Scheme extends SessionAuthenticationScheme {
308308
}
309309
},
310310
// Token exchange step
311-
tokenExchange: class extends AuthStep {
311+
tokenExchange: class extends BaseApiEndpoint {
312312
override path = '/token';
313313
override description = 'Exchange authorization code for access token and session';
314314

@@ -330,11 +330,11 @@ After a session is obtained through the auth flow, each request:
330330
3. Validates the session is not expired
331331
4. Sets `request.authenticated = true` and allows request to continue
332332

333-
## AuthFlow & AuthStep
333+
## AuthFlow & BaseApiEndpoint
334334

335-
An `AuthFlow` is a named collection of `AuthStep` classes representing the complete multi-step authentication process used to obtain a session.
335+
An `AuthFlow` is a named collection of `BaseApiEndpoint` classes representing the complete multi-step authentication process used to obtain a session.
336336

337-
**AuthStep** is an abstract class that extends `BaseApiEndpoint`, allowing each step to leverage endpoint features:
337+
**BaseApiEndpoint** is the abstract class used for authentication flow steps, allowing each step to leverage endpoint features:
338338
- Request validation (body, query, params, headers via valsan)
339339
- Middleware support
340340
- Standardized error handling
@@ -349,7 +349,7 @@ See [src/authentication/auth-step.ts](../src/authentication/auth-step.ts) and [s
349349

350350
**Step example with validation:**
351351
```typescript
352-
class TokenExchangeStep extends AuthStep {
352+
class TokenExchangeStep extends BaseApiEndpoint {
353353
override path = '/token';
354354
override description = 'Exchange code for tokens';
355355

@@ -436,7 +436,7 @@ The authentication module exports the following. See [src/authentication/index.t
436436
- `AuthenticationScheme` - Base class for all schemes
437437
- `InlineAuthenticationScheme` - For credential-based auth
438438
- `SessionAuthenticationScheme` - For stateful multi-step auth
439-
- `AuthStep` - Interface for auth flow steps
439+
- `BaseApiEndpoint` - Base class for auth flow steps
440440
- `AuthFlow` - Type for collections of steps
441441
- `BearerAuthenticationScheme` - Built-in Bearer token scheme
442442
- `AuthenticatedRequest` - Request type with auth flag

examples/complete-example/auth/session-auth.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
BaseApiEndpoint,
1111
SessionAuthenticationScheme,
1212
AuthFlow,
13-
AuthStep,
1413
InMemorySessionDriver,
1514
ApiRequest,
1615
ApiResponse,
@@ -56,7 +55,7 @@ class OAuth2Scheme extends SessionAuthenticationScheme {
5655
/**
5756
* Challenge step - generates a random challenge for CSRF prevention
5857
*/
59-
class ChallengeStep extends AuthStep {
58+
class ChallengeStep extends BaseApiEndpoint {
6059
override path = '/challenge';
6160
override method = EndpointMethod.POST;
6261
override description = 'Generate authentication challenge';
@@ -73,7 +72,7 @@ class OAuth2Scheme extends SessionAuthenticationScheme {
7372
/**
7473
* Authorization step - validates credentials
7574
*/
76-
class AuthorizationStep extends AuthStep {
75+
class AuthorizationStep extends BaseApiEndpoint {
7776
override path = '/authorize';
7877
override method = EndpointMethod.POST;
7978
override description = 'Authorize with username and password';
@@ -128,7 +127,7 @@ class OAuth2Scheme extends SessionAuthenticationScheme {
128127
/**
129128
* Token exchange step - exchanges code for session
130129
*/
131-
class TokenStep extends AuthStep {
130+
class TokenStep extends BaseApiEndpoint {
132131
override path = '/token';
133132
override method = EndpointMethod.POST;
134133
override description = 'Exchange authorization code for session';

src/authentication/auth-flow.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { AuthStepConstructor } from './auth-step';
1+
import { ApiEndpoint } from '../router';
22

33
/**
44
* A named collection of authentication steps
55
* Each step is identified by a key
66
* (e.g., 'challenge', 'authorization', 'tokenExchange')
77
*
8-
* Steps are classes extending AuthStep (which extends BaseApiEndpoint)
8+
* Steps are classes extending BaseApiEndpoint
99
* This allows each step to use endpoint features like validation and middleware
1010
*
1111
* The flow represents the entire authentication process
1212
* Steps are executed in the order they're defined
1313
*/
14-
export type AuthFlow = Record<string, AuthStepConstructor>;
14+
export type AuthFlow = Record<string, ApiEndpoint>;

src/authentication/auth-step.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +0,0 @@
1-
import { BaseApiEndpoint } from '../router';
2-
3-
/**
4-
* Abstract base class for authentication flow steps
5-
*/
6-
export abstract class AuthStep extends BaseApiEndpoint {}
7-
8-
export type AuthStepConstructor = new (
9-
...params: ConstructorParameters<typeof AuthStep>
10-
) => AuthStep;

src/authentication/authentication-scheme.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export abstract class AuthenticationScheme {
6060

6161
/**
6262
* Get the Express middleware for this authentication scheme
63-
* For InlineAuthenticationScheme: runs AuthStep
63+
* For InlineAuthenticationScheme: runs endpoint
6464
* For SessionAuthenticationScheme: verifies session
6565
*
6666
* @returns Express RequestHandler middleware

src/authentication/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@ export {
44
InlineAuthenticationScheme,
55
SessionAuthenticationScheme,
66
} from './authentication-scheme';
7-
export { AuthStep } from './auth-step';
87
export { AuthFlow } from './auth-flow';
98
export * from './schemes';

src/router/endpoint.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,6 @@ export abstract class BaseApiEndpoint extends BaseApiRoute {
124124
}
125125
}
126126

127-
export type ApiEndpoint = { new (): BaseApiEndpoint };
127+
export type ApiEndpoint = new (
128+
...params: ConstructorParameters<typeof BaseApiEndpoint>
129+
) => BaseApiEndpoint;

test/spec/authentication/session-scheme.spec.ts

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import 'jasmine';
22
import {
33
SessionAuthenticationScheme,
44
AuthFlow,
5-
AuthStep,
65
} from '../../../src/authentication';
7-
import { BaseApiRouter } from '../../../src/router';
6+
import { BaseApiRouter, BaseApiEndpoint } from '../../../src/router';
87
import { InMemorySessionDriver } from '../../../src/session';
98
import { SessionDriver } from '../../../src/session';
109
import { Session } from '../../../src/session/session';
@@ -20,7 +19,7 @@ type AnyResponse = any;
2019
abstract class TestSessionDriverImport extends SessionDriver {}
2120

2221
// eslint-disable-next-line @typescript-eslint/no-unused-vars
23-
abstract class TestAuthFlowImport extends AuthStep {}
22+
abstract class TestAuthFlowImport extends BaseApiEndpoint {}
2423

2524
/**
2625
* Test implementation of SessionAuthenticationScheme
@@ -46,7 +45,7 @@ class TestSessionScheme extends SessionAuthenticationScheme {
4645

4746
public getAuthFlow(): AuthFlow {
4847
return {
49-
testStep: class extends AuthStep {
48+
testStep: class extends BaseApiEndpoint {
5049
override description = 'Test step';
5150
async handle() {
5251
return {};
@@ -273,7 +272,7 @@ describe('SessionAuthenticationScheme', () => {
273272

274273
public getAuthFlow(): AuthFlow {
275274
return {
276-
customStep: class extends AuthStep {
275+
customStep: class extends BaseApiEndpoint {
277276
override description = 'Custom step';
278277
async handle() {
279278
return {};
@@ -506,21 +505,21 @@ describe('SessionAuthenticationScheme', () => {
506505
});
507506

508507
it('should expose all auth steps as routes', () => {
509-
class ChallengeStep extends AuthStep {
508+
class ChallengeStep extends BaseApiEndpoint {
510509
override path = '/challenge';
511510
async handle() {
512511
return { challenge: 'test' };
513512
}
514513
}
515514

516-
class AuthStep2 extends AuthStep {
515+
class AuthStep2 extends BaseApiEndpoint {
517516
override path = '/authorize';
518517
async handle() {
519518
return { code: 'abc123' };
520519
}
521520
}
522521

523-
class TokenStep extends AuthStep {
522+
class TokenStep extends BaseApiEndpoint {
524523
override path = '/token';
525524
async handle() {
526525
return { token: 'xyz789' };
@@ -588,14 +587,14 @@ describe('SessionAuthenticationScheme', () => {
588587
});
589588

590589
it('should return auth steps from routes() method', async () => {
591-
class ChallengeStep extends AuthStep {
590+
class ChallengeStep extends BaseApiEndpoint {
592591
override path = '/challenge';
593592
async handle() {
594593
return { challenge: 'test' };
595594
}
596595
}
597596

598-
class TokenStep extends AuthStep {
597+
class TokenStep extends BaseApiEndpoint {
599598
override path = '/token';
600599
async handle() {
601600
return { token: 'xyz789' };
@@ -676,14 +675,14 @@ describe('SessionAuthenticationScheme', () => {
676675

677676
public getAuthFlow(): AuthFlow {
678677
return {
679-
login: class extends AuthStep {
678+
login: class extends BaseApiEndpoint {
680679
override path = '/login';
681680
override description = 'Login step';
682681
async handle() {
683682
return { token: 'abc123' };
684683
}
685684
},
686-
logout: class extends AuthStep {
685+
logout: class extends BaseApiEndpoint {
687686
override path = '/logout';
688687
override description = 'Logout step';
689688
async handle() {
@@ -731,21 +730,21 @@ describe('SessionAuthenticationScheme', () => {
731730
}
732731

733732
public getAuthFlow(): AuthFlow {
734-
class Step1 extends AuthStep {
733+
class Step1 extends BaseApiEndpoint {
735734
override path = '/step1';
736735
async handle() {
737736
return { step: 1 };
738737
}
739738
}
740739

741-
class Step2 extends AuthStep {
740+
class Step2 extends BaseApiEndpoint {
742741
override path = '/step2';
743742
async handle() {
744743
return { step: 2 };
745744
}
746745
}
747746

748-
class Step3 extends AuthStep {
747+
class Step3 extends BaseApiEndpoint {
749748
override path = '/step3';
750749
async handle() {
751750
return { step: 3 };
@@ -764,7 +763,7 @@ describe('SessionAuthenticationScheme', () => {
764763
const endpoints = scheme.getEndpoints();
765764

766765
expect(endpoints.length).toBe(3);
767-
// All should be AuthStep classes
766+
// All should be endpoint classes
768767
endpoints.forEach((endpoint) => {
769768
// eslint-disable-next-line @typescript-eslint/no-explicit-any
770769
expect(typeof endpoint).toBe('function');
@@ -831,13 +830,13 @@ describe('SessionAuthenticationScheme', () => {
831830

832831
public getAuthFlow(): AuthFlow {
833832
return {
834-
auth: class extends AuthStep {
833+
auth: class extends BaseApiEndpoint {
835834
override path = '/auth';
836835
async handle() {
837836
return { authenticated: true };
838837
}
839838
},
840-
verify: class extends AuthStep {
839+
verify: class extends BaseApiEndpoint {
841840
override path = '/verify';
842841
async handle() {
843842
return { verified: true };

0 commit comments

Comments
 (0)