-
Notifications
You must be signed in to change notification settings - Fork 1
/
authentication.controller.ts
122 lines (110 loc) · 5.08 KB
/
authentication.controller.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import { injectable } from "inversify";
import { Controller, ApiController, HttpPost, Async } from "dinoloop";
import { components } from "./components";
import { v4 as uuid } from "uuid";
import { AuthenticationService, Token, unauthorized } from "../services/authentication.service";
import { SessionRepository, SessionFactory } from "../model/session.model";
import moment from "moment";
import { requireDefined } from "../lib/assertions";
import { SignatureService } from "../services/signature.service";
import { OpenAPIV3 } from "express-oas-generator";
import { getRequestBody, getBodyContent, getDefaultResponses, addPathParameter, addTag, setControllerTag } from "./doc";
export function fillInSpec(spec: OpenAPIV3.Document): void {
const tagName = 'Authentication';
addTag(spec, {
name: tagName,
description: "Handling of session authentication"
});
setControllerTag(spec, /^\/api\/auth.*/, tagName);
AuthenticationController.signIn(spec);
AuthenticationController.authenticate(spec);
}
type SignInRequestView = components["schemas"]["SignInRequestView"];
type SignInResponseView = components["schemas"]["SignInResponseView"];
type AuthenticateRequestView = components["schemas"]["AuthenticateRequestView"];
type AuthenticateResponseView = components["schemas"]["AuthenticateResponseView"];
type SignatureView = components["schemas"]["SignatureView"];
@injectable()
@Controller('/auth')
export class AuthenticationController extends ApiController {
static readonly RESOURCE = "authentication";
constructor(
private sessionRepository: SessionRepository,
private sessionFactory: SessionFactory,
private signatureService: SignatureService,
private authenticationService: AuthenticationService) {
super();
}
static signIn(spec: OpenAPIV3.Document) {
const operationObject = spec.paths["/api/auth/sign-in"].post!;
operationObject.summary = "Sign-in for a new session";
operationObject.description = "No signature required";
operationObject.requestBody = getRequestBody({
description: "Session creation data",
view: "SignInRequestView"
})
operationObject.responses = {"200": {
description: "OK",
content: getBodyContent("SignInResponseView"),
}};
}
@HttpPost('/sign-in')
@Async()
async signIn(signInRequest: SignInRequestView): Promise<SignInResponseView> {
const sessionId = uuid();
const createdOn = moment();
signInRequest.addresses?.forEach(address => {
const session = this.sessionFactory.newSession({
userAddress: address,
sessionId,
createdOn
});
this.sessionRepository.save(session);
}
)
return Promise.resolve({ sessionId });
}
static authenticate(spec: OpenAPIV3.Document) {
const operationObject = spec.paths["/api/auth/{sessionId}/authenticate"].post!;
operationObject.summary = "Authenticate the given session";
operationObject.description = "<p>The signature's resource is <code>authentication</code>, the operation <code>login</code> and the additional field is <code>sessionId</code><p>";
operationObject.requestBody = getRequestBody({
description: "Authentication data",
view: "AuthenticateRequestView",
});
operationObject.responses = getDefaultResponses("AuthenticateResponseView");
addPathParameter(operationObject, 'sessionId', "The ID of the session to authenticate");
}
@HttpPost('/:sessionId/authenticate')
@Async()
async authenticate(
authenticateRequest: AuthenticateRequestView,
sessionId: string): Promise<AuthenticateResponseView> {
let response: AuthenticateResponseView = { tokens: {} };
for (let address in authenticateRequest.signatures) {
const signature: SignatureView = authenticateRequest.signatures[address];
const token = await this.authenticateSession(address, sessionId, signature);
response.tokens![address] = { value: token.value, expiredOn: token.expiredOn.toISOString() }
}
return Promise.resolve(response);
}
private async authenticateSession(address: string, sessionId: string, signature: SignatureView): Promise<Token> {
const session = await this.sessionRepository.find(address, sessionId);
if (session === undefined) {
throw unauthorized("Invalid session")
}
await this.sessionRepository.delete(session);
if (!await this.signatureService.verify({
signature: requireDefined(signature.signature),
address: address,
resource: AuthenticationController.RESOURCE,
operation: "login",
timestamp: requireDefined(signature.signedOn),
attributes: [ sessionId ]
})) {
throw unauthorized("Invalid signature");
} else {
return this.authenticationService.createToken(address, moment());
}
}
}