-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathauth-action.provider.ts
153 lines (144 loc) · 4.67 KB
/
auth-action.provider.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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved.
// Node module: @loopback/authentication
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {
config,
Getter,
inject,
injectable,
Provider,
Setter,
} from '@loopback/core';
import {
asMiddleware,
Middleware,
RedirectRoute,
Request,
RestMiddlewareGroups,
} from '@loopback/rest';
import {SecurityBindings, UserProfile} from '@loopback/security';
import {AuthenticationBindings} from '../keys';
import {
AuthenticateFn,
AuthenticationOptions,
AuthenticationStrategy,
AUTHENTICATION_STRATEGY_NOT_FOUND,
USER_PROFILE_NOT_FOUND,
} from '../types';
/**
* Provides the authentication action for a sequence
* @example
* ```ts
* context.bind('authentication.actions.authenticate').toProvider(AuthenticateActionProvider)
* ```
*/
export class AuthenticateActionProvider implements Provider<AuthenticateFn> {
constructor(
// The provider is instantiated for Sequence constructor,
// at which time we don't have information about the current
// route yet. This information is needed to determine
// what auth strategy should be used.
// To solve this, we are injecting a getter function that will
// defer resolution of the strategy until authenticate() action
// is executed.
@inject.getter(AuthenticationBindings.STRATEGY)
readonly getStrategies: Getter<
AuthenticationStrategy | AuthenticationStrategy[] | undefined
>,
@inject.setter(SecurityBindings.USER)
readonly setCurrentUser: Setter<UserProfile>,
@inject.setter(AuthenticationBindings.AUTHENTICATION_REDIRECT_URL)
readonly setRedirectUrl: Setter<string>,
@inject.setter(AuthenticationBindings.AUTHENTICATION_REDIRECT_STATUS)
readonly setRedirectStatus: Setter<number>,
@config({fromBinding: AuthenticationBindings.COMPONENT})
private readonly options: AuthenticationOptions = {},
) {}
/**
* @returns authenticateFn
*/
value(): AuthenticateFn {
return request => this.action(request);
}
/**
* The implementation of authenticate() sequence action.
* @param request - The incoming request provided by the REST layer
*/
async action(request: Request): Promise<UserProfile | undefined> {
let strategies = await this.getStrategies();
if (strategies == null) {
// The invoked operation does not require authentication.
return undefined;
}
// convert to array if required
strategies = Array.isArray(strategies) ? strategies : [strategies];
const authErrors: unknown[] = [];
for (const strategy of strategies) {
let authResponse: UserProfile | RedirectRoute | undefined = undefined;
try {
authResponse = await strategy.authenticate(request);
} catch (err) {
if (this.options.failOnError) {
throw err;
}
authErrors.push(err);
}
// response from `strategy.authenticate()` could return an object of
// type UserProfile or RedirectRoute
if (RedirectRoute.isRedirectRoute(authResponse)) {
const redirectOptions = authResponse;
// bind redirection url and status to the context
// controller should handle actual redirection
this.setRedirectUrl(redirectOptions.targetLocation);
this.setRedirectStatus(redirectOptions.statusCode);
return;
} else if (authResponse != null) {
// if `strategy.authenticate()` returns an object of type UserProfile,
// set it as current user
const userProfile = authResponse as UserProfile;
this.setCurrentUser(userProfile);
return userProfile;
}
}
if (authErrors.length) {
throw authErrors[0];
}
// important to throw a non-protocol-specific error here
const error = new Error(
`User profile not returned from strategy's authenticate function`,
);
Object.assign(error, {
code: USER_PROFILE_NOT_FOUND,
});
throw error;
}
}
@injectable(
asMiddleware({
group: RestMiddlewareGroups.AUTHENTICATION,
upstreamGroups: [RestMiddlewareGroups.FIND_ROUTE],
}),
)
export class AuthenticationMiddlewareProvider implements Provider<Middleware> {
constructor(
@inject(AuthenticationBindings.AUTH_ACTION)
private authenticate: AuthenticateFn,
) {}
value(): Middleware {
return async (ctx, next) => {
try {
await this.authenticate(ctx.request);
} catch (error) {
if (
error.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
error.code === USER_PROFILE_NOT_FOUND
) {
error.statusCode = 401;
}
throw error;
}
return next();
};
}
}