@hitchy/plugin-auth 0.2.0 → 0.3.2
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/.gitlab-ci.yml +42 -21
- package/LICENSE +21 -0
- package/api/controller/user.js +2 -29
- package/api/model/authorization/rule.js +0 -27
- package/api/model/role.js +0 -28
- package/api/model/user-to-role.js +0 -28
- package/api/model/user.js +2 -30
- package/api/policy/authentication.js +127 -70
- package/api/policy/authorization.js +0 -28
- package/api/policy/user.js +0 -28
- package/api/service/auth/manager.js +45 -32
- package/api/service/authentication/passport.js +7 -34
- package/api/service/authentication/strategies.js +230 -58
- package/api/service/authorization/node.js +0 -28
- package/api/service/authorization/policy-generator.js +0 -28
- package/api/service/authorization/tree.js +8 -36
- package/config/auth.js +5 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +79 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +231 -0
- package/coverage/plugin-auth/api/controller/index.html +111 -0
- package/coverage/plugin-auth/api/controller/user.js.html +368 -0
- package/coverage/plugin-auth/api/model/authorization/index.html +111 -0
- package/coverage/plugin-auth/api/model/authorization/rule.js.html +227 -0
- package/coverage/plugin-auth/api/model/index.html +141 -0
- package/coverage/plugin-auth/api/model/role.js.html +200 -0
- package/coverage/plugin-auth/api/model/user-to-role.js.html +167 -0
- package/coverage/plugin-auth/api/model/user.js.html +752 -0
- package/coverage/plugin-auth/api/policy/authentication.js.html +782 -0
- package/coverage/plugin-auth/api/policy/authorization.js.html +182 -0
- package/coverage/plugin-auth/api/policy/index.html +141 -0
- package/coverage/plugin-auth/api/policy/user.js.html +479 -0
- package/coverage/plugin-auth/api/service/auth/index.html +111 -0
- package/coverage/plugin-auth/api/service/auth/manager.js.html +959 -0
- package/coverage/plugin-auth/api/service/authentication/index.html +126 -0
- package/coverage/plugin-auth/api/service/authentication/passport.js.html +293 -0
- package/coverage/plugin-auth/api/service/authentication/strategies.js.html +929 -0
- package/coverage/plugin-auth/api/service/authorization/index.html +141 -0
- package/coverage/plugin-auth/api/service/authorization/node.js.html +944 -0
- package/coverage/plugin-auth/api/service/authorization/policy-generator.js.html +386 -0
- package/coverage/plugin-auth/api/service/authorization/tree.js.html +983 -0
- package/coverage/plugin-auth/config/auth.js.html +140 -0
- package/coverage/plugin-auth/config/index.html +111 -0
- package/coverage/plugin-auth/index.html +111 -0
- package/coverage/plugin-auth/index.js.html +344 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +170 -0
- package/coverage/tmp/coverage-8472-1648414315419-0.json +1 -0
- package/docs/.vuepress/config.js +5 -2
- package/docs/api/config.md +14 -2
- package/docs/api/model/authorization-rule.md +1 -1
- package/docs/api/model/user.md +2 -2
- package/docs/api/service/authentication-passport.md +1 -1
- package/docs/guides/getting-started.md +2 -2
- package/docs/guides/idp-login.png +0 -0
- package/docs/guides/idp-saml-cert.png +0 -0
- package/docs/guides/openid-connect.md +164 -0
- package/docs/guides/readme.md +2 -0
- package/docs/guides/saml.md +161 -0
- package/docs/introduction.md +5 -5
- package/index.js +31 -51
- package/package.json +18 -13
- package/readme.md +11 -43
package/docs/.vuepress/config.js
CHANGED
|
@@ -42,10 +42,13 @@ module.exports = {
|
|
|
42
42
|
{ text: "API", link: "/api/" },
|
|
43
43
|
{ text: "Hitchy", items: [
|
|
44
44
|
{ text: "Core", link: "https://core.hitchy.org/" },
|
|
45
|
-
{ text: "", items: [
|
|
45
|
+
{ text: "Plugins", items: [
|
|
46
46
|
{ text: "Odem", link: "https://odem.hitchy.org/" },
|
|
47
47
|
{ text: "Auth", link: "/" },
|
|
48
|
-
]}
|
|
48
|
+
] },
|
|
49
|
+
{ text: "Tools", items: [
|
|
50
|
+
{ text: "SDT", link: "https://sdt.hitchy.org/" },
|
|
51
|
+
] }
|
|
49
52
|
] },
|
|
50
53
|
],
|
|
51
54
|
},
|
package/docs/api/config.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
prev:
|
|
2
|
+
prev: /api/service/
|
|
3
3
|
next: routing.md
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -48,6 +48,18 @@ module.exports = {
|
|
|
48
48
|
Environment variables **HITCHY_ADMIN_NAME**, **HITCHY_ADMIN_PASSWORD** and **HITCHY_ADMIN_ROLE** can be used to override any configuration provided here to e.g. support container-driven setups.
|
|
49
49
|
:::
|
|
50
50
|
|
|
51
|
+
## config.auth.roles
|
|
52
|
+
|
|
53
|
+
Lists role names to create on boot if missing in local database. This list is empty by default.
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
module.exports = {
|
|
57
|
+
auth: {
|
|
58
|
+
roles: [ "guest", "customer", "manager" ],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
51
63
|
## config.auth.authorizations
|
|
52
64
|
|
|
53
65
|
This section grants or revokes access on named resources to/from users and/or roles. These _authorizations_ are loaded on application start before reading custom rules from local database.
|
|
@@ -165,4 +177,4 @@ Provide a custom local strategy in property `local` here to prevent built-in [lo
|
|
|
165
177
|
|
|
166
178
|
This optional string names strategy to use by default. Defaults to `local` itself.
|
|
167
179
|
|
|
168
|
-
Any custom strategy named here must be [set up](#config-auth-strategies) properly.
|
|
180
|
+
Any custom strategy named here must be [set up](#config-auth-strategies) properly.
|
package/docs/api/model/user.md
CHANGED
|
@@ -25,9 +25,9 @@ Passwords for use with local authentication are implicitly hashed with [SCRYPT](
|
|
|
25
25
|
|
|
26
26
|
Every user can be associated with a particular passport strategy to use for authentication. This [string](https://odem.hitchy.org/guides/defining-models.html#strings) property is picking a strategy by its name. Default strategy as configured is used in case this property is empty or unset.
|
|
27
27
|
|
|
28
|
-
###
|
|
28
|
+
### strategyData
|
|
29
29
|
|
|
30
|
-
This [string](https://odem.hitchy.org/guides/defining-models.html#strings) property is meant to provide
|
|
30
|
+
This [string](https://odem.hitchy.org/guides/defining-models.html#strings) property is meant to provide custom data specific to selected strategy. It's format is specific to that strategy. It might be used to pick a remote service confirming the user's authentication or represent additional meta data on it.
|
|
31
31
|
|
|
32
32
|
## Methods
|
|
33
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
prev: ../guides/
|
|
3
|
-
next:
|
|
3
|
+
next: openid-connect.md
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Quick Start
|
|
@@ -66,4 +66,4 @@ Content-Type: application/x-www-form-urlencoded
|
|
|
66
66
|
username=admin&password=nimda
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
This works due to the default [configuration](../api/config.md#config-auth-admin) for implicitly [setting up administrator account](
|
|
69
|
+
This works due to the default [configuration](../api/config.md#config-auth-admin) for implicitly [setting up administrator account](/api/service/auth-manager.md#createadminifmissing) on application start.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
prev: getting-started.md
|
|
3
|
+
next: saml.md
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OpenID Connect
|
|
7
|
+
|
|
8
|
+
[OpenID Connect](https://en.wikipedia.org/wiki/OpenID#OpenID_Connect_(OIDC)) is a security protocol for authenticating in distributed applications as a centrally managed user without exposing its credentials to either application.
|
|
9
|
+
|
|
10
|
+
OpenID Connect supports different flows. This example is about [Authorization Code Flow](https://auth0.com/docs/flows/authorization-code-flow).
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
This protocol depends on a remotely set up _identity provider_ (IdP). This tutorial is illustrating how to enable a Hitchy-based application to support authentication against such an IdP via OpenID Connect. Setting up an IdP is beyond its scope, though.
|
|
15
|
+
|
|
16
|
+
There are plenty of solutions available for running your own IdP. There are multiple options including commercial and open-source software. [Keycloak](https://www.keycloak.org/) and [authentik](https://goauthentik.io/) are examples for the latter. See our rough [step-by-step tutorial for setting up Keycloak on a server using a stack of Docker containers](https://gist.github.com/soletan/02e141993a811221ce8347c5a6129021). This tutorial is based on Keycloak.
|
|
17
|
+
|
|
18
|
+
## Create custom strategy
|
|
19
|
+
|
|
20
|
+
This plugin relies on [passport.js](https://www.passportjs.org/) which in turn supports so called _strategies_ to support different kinds of authentication services and security protocols. The strategy used in this example is included with 3rd-party package [openid-client](https://www.npmjs.com/package/openid-client) which you need to install in context of your application:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm i openid-client
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**@hitchy/plugin-auth** is picking up all [strategies configured in its runtime configuration](../api/config.md#config-auth-strategies) on application start. It includes a factory service offering methods for generating strategies from a set of configuration options. That's what we use here.
|
|
27
|
+
|
|
28
|
+
Open file **config/auth.js** of your project and add the `oidc` property to the list of strategies as illustrated in this example:
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
"use strict";
|
|
32
|
+
|
|
33
|
+
module.exports = async function() {
|
|
34
|
+
const { service } = this.runtime;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
auth: {
|
|
38
|
+
strategies: {
|
|
39
|
+
oidc: await service.AuthenticationStrategies.generateOpenIdConnect( "oidc", {
|
|
40
|
+
// Provides URL of IdP for discovering settings for this
|
|
41
|
+
// application's authentication realm.
|
|
42
|
+
discovery_url: "https://idp.cepharum.de/auth/realms/hitchy-plugin-auth-ci-test",
|
|
43
|
+
|
|
44
|
+
// Provides this application's name in context of discovered
|
|
45
|
+
// IdP realm. Think of it as the application's username for
|
|
46
|
+
// authenticating itself at the IdP.
|
|
47
|
+
client_id: "ci-openid-test",
|
|
48
|
+
|
|
49
|
+
// Provides IdP-specific secret used to authenticate this
|
|
50
|
+
// application as a valid client of IdP. Think of it as the
|
|
51
|
+
// application's password for authenticating itself.
|
|
52
|
+
client_secret: "some-random-secret",
|
|
53
|
+
|
|
54
|
+
// Lists valid URLs user may be redirected to after logging
|
|
55
|
+
// in successfully at IdP.
|
|
56
|
+
redirect_uris: ["http://localhost:3000/api/auth/login/oidc"],
|
|
57
|
+
|
|
58
|
+
// Lists metadata/attributes of authenticated user to be
|
|
59
|
+
// delivered by IdP in redirect after successful login.
|
|
60
|
+
response_types: ["code"],
|
|
61
|
+
|
|
62
|
+
// Lists valid URLs user may be redirected to after logging
|
|
63
|
+
// out from IdP.
|
|
64
|
+
post_logout_redirect_uris: ["http://localhost:3000/api/auth/logout"],
|
|
65
|
+
} ),
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
:::tip Multiple strategies?
|
|
73
|
+
It's fine to have multiple strategies of different name being set up like that. Just make sure name of property is different, URL-safe and provided as first argument to the generator method.
|
|
74
|
+
:::
|
|
75
|
+
|
|
76
|
+
The strategy in this example is named `oidc`. Thus, the same name is given as first argument to the generator function, too. Its second argument provides configuration options for used to set up the strategy in detail.
|
|
77
|
+
|
|
78
|
+
:::warning Adapt to your IdP
|
|
79
|
+
The configuration in code example is for illustration, only. You need to adapt the settings to work with your IdP.
|
|
80
|
+
:::
|
|
81
|
+
|
|
82
|
+
* The **discovery_url** is addressing your IdP's meta settings which is a computable set of configuration parameters the client will pick up and adapt to your IdP properly. In our case, which is based on Keycloak, it is [https://idp.cepharum.de/auth/realms/hitchy-plugin-auth-ci-test](https://idp.cepharum.de/auth/realms/hitchy-plugin-auth-ci-test). Click the link to see all meta settings related to a realm named `hitchy-plugin-auth-ci-test`.
|
|
83
|
+
* **client_id** and **client_secret** are just like a user's name and password, but this time they are suitable for authenticating your application at your IdP.
|
|
84
|
+
* A list of **redirect_uris** can be provided, but usually it consists of a single URL, only. In Authorization Code Flow, users are redirected to that URL right after successful authentication. Additional data on authenticated user is passed in query parameters there. The strategy is required to process that request, too. Thus, in case of **hitchy/plugin-auth**, this is usually referring to the same login endpoint used to trigger the authentication in the first place, e.g. [http://localhost:3000/api/auth/login/oidc](http://localhost:3000/api/auth/login/oidc). This support is based on some [routing defaults](../api/routing.md).
|
|
85
|
+
* Similar to that, another list of possible redirect URIs is given in **post_logout_redirect_uris**, this time for redirecting a user's browser after successful logout at IdP. The target is meant to finish the logout process on behalf of your application, thus you should point it to the logout URL [http://localhost:3000/api/auth/logout](http://localhost:3000/api/auth/logout) accordingly.
|
|
86
|
+
* A list of required meta attributes to deliver on an authenticated user can be configured, too. Requesting `code` response type is essential to Authorization Code Flow. That's referring to the user's name. You might query for additional data such as the user's mail address or similar.
|
|
87
|
+
|
|
88
|
+
:::tip Additional options
|
|
89
|
+
All but the **discovery_url** are metadata options supported by the underlying **openid-client** package, thus you should look into [its documentation](https://github.com/panva/node-openid-client/blob/main/docs/README.md#new-clientmetadata-jwks-options) for additional options available. Some of them might be failing to work because of the way the client is integrated with Hitchy.
|
|
90
|
+
:::
|
|
91
|
+
|
|
92
|
+
## Restart and test
|
|
93
|
+
|
|
94
|
+
Restart your hitchy instance. Open browser at [http://localhot:3000/api/auth/current](http://localhot:3000/api/auth/current) and get a response like this one:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{"success":true,"authenticated":false}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The request has succeeded, but no user is authenticated currently.
|
|
101
|
+
|
|
102
|
+
Trigger authentication by opening URL [http://localhost:3000/api/auth/login/oidc](http://localhost:3000/api/auth/login/oidc) next. Last segment in this URL is referring to the strategy name you've picked when integrating it with the list of configured strategies.
|
|
103
|
+
|
|
104
|
+
This will redirect the browser to your IdP for prompting to log in:
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
108
|
+
After logging in there, your browser is instantly redirected back to the URL [http://localhost:3000/api/auth/login/oidc](http://localhost:3000/api/auth/login/oidc) as given in configuration above. This time it is succeeding with a response:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{"success":true,"authenticated":true}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
You are authenticated!
|
|
115
|
+
|
|
116
|
+
Return to the first URL requested above. It's providing a different set of information this time:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{"success":true,"authenticated":{"uuid":"d778afae-1234-4a58-a254-b56b1f36e914","name":"your-user","roles":[]}}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Try repeating this request if you like. Unless closing your browser, you stay authenticated.
|
|
123
|
+
|
|
124
|
+
If you happen to close the browser, just return to the login URL and - based on your IdP configuration - you will be re-authenticated instantly without being prompted for entering username and password again.
|
|
125
|
+
|
|
126
|
+
Next send a request to [http://localhost:3000/api/auth/logout](http://localhost:3000/api/auth/logout). It gets approved:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{"success":true}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Fetch current state of authentication at [https://localhost:3000/api/auth/current](https://localhost:3000/api/auth/current) once again.
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{"success":true,"authenticated":false}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Try to re-authenticate instantly at [http://localhost:3000/api/auth/login/oidc](http://localhost:3000/api/auth/login/oidc). It's going to prompt for username and password at your IdP again.
|
|
139
|
+
|
|
140
|
+
## How to welcome users?
|
|
141
|
+
|
|
142
|
+
Well, if you don't want your application's users to see the approval of logging in as raw JSON data, you simply have to replace the controller for route `GET /api/auth/login/oidc` and make it provide any other response, e.g. some redirection to a welcome page of your application.
|
|
143
|
+
|
|
144
|
+
Adjust your application's file **config/routes.js** accordingly:
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
exports.routes = {
|
|
148
|
+
"GET /api/auth/login/oidc": ( _, res ) => res.redirect( 301, "/welcome" ),
|
|
149
|
+
};
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
This is possible in a custom policy [processed after this plugin's policy](https://core.hitchy.org/internals/routing-basics.html#routing-slots), either. This could be your **config/policies.js** file:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
exports.policies = {
|
|
156
|
+
"GET /api/auth/login/oidc"( req, res, next ) {
|
|
157
|
+
if ( req.user ) {
|
|
158
|
+
res.redirect( 301, "/welcome" );
|
|
159
|
+
} else {
|
|
160
|
+
next();
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
```
|
package/docs/guides/readme.md
CHANGED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
prev: openid-connect.md
|
|
3
|
+
next: ../api/
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SAML 2.0
|
|
7
|
+
|
|
8
|
+
[SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0) is a security protocol for authenticating centrally managed users in multiple distributed applications without exposing their credentials to either application.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
This protocol depends on a remotely set up identity provider (IdP). This tutorial is illustrating how to enable a Hitchy-based application to support authentication against such an IdP via SAML 2.0. Setting up an IdP is beyond its scope, though.
|
|
13
|
+
|
|
14
|
+
There are plenty of solutions available for running your own IdP. There are multiple options including commercial and open-source software. [Keycloak](https://www.keycloak.org/) and [authentik](https://goauthentik.io/) are examples for the latter. See our rough [step-by-step tutorial for setting up Keycloak on a server using a stack of Docker containers](https://gist.github.com/soletan/02e141993a811221ce8347c5a6129021). This tutorial is based on Keycloak.
|
|
15
|
+
|
|
16
|
+
## Create custom strategy
|
|
17
|
+
|
|
18
|
+
This plugin relies on [passport.js](https://www.passportjs.org/) which in turn supports so called _strategies_ to support different kinds of authentication services and security protocols. The strategy used in this example is included with 3rd-party package [passport-saml](https://www.npmjs.com/package/passport-saml) which you need to install in context of your application:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm i passport-saml
|
|
22
|
+
```
|
|
23
|
+
**@hitchy/plugin-auth** is picking up all [strategies configured in its runtime configuration](../api/config.md#config-auth-strategies) on application start. It includes a factory service offering methods for generating strategies from a set of configuration options. That's what we use here.
|
|
24
|
+
|
|
25
|
+
Open file **config/auth.js** of your project and add the `saml` property to the list of strategies as illustrated in this example:
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
"use strict";
|
|
29
|
+
|
|
30
|
+
module.exports = async function() {
|
|
31
|
+
const { service } = this.runtime;
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
auth: {
|
|
35
|
+
strategies: {
|
|
36
|
+
saml: service.AuthenticationStrategies.generateSaml( "saml", {
|
|
37
|
+
// URL of IdP receiving SAML requests
|
|
38
|
+
entryPoint: "https://idp.cepharum.de/auth/realms/hitchy-plugin-auth-ci-test/protocol/saml",
|
|
39
|
+
|
|
40
|
+
// name of this client as registered with the IdP
|
|
41
|
+
issuer: "ci-saml-test",
|
|
42
|
+
|
|
43
|
+
// signature algorithm to use (set to prevent insecure
|
|
44
|
+
// SHA-1 used by default)
|
|
45
|
+
signatureAlgorithm: "sha256",
|
|
46
|
+
|
|
47
|
+
// URL for redirecting user to after successful login
|
|
48
|
+
callbackUrl: "http://localhost:3000/api/auth/login/saml",
|
|
49
|
+
|
|
50
|
+
// URL of local application's endpoint for logging
|
|
51
|
+
// out to be exposed as meta of this service provider
|
|
52
|
+
logoutCallbackUrl: "http://localhost:3000/api/auth/logout",
|
|
53
|
+
|
|
54
|
+
// public certificate of IdP's realm used for signing
|
|
55
|
+
// SAML responses
|
|
56
|
+
cert: "MIICwzCCAasCfB5l4jANBg...SDuuWWgsPnlrNpCnOnM6ycT/PCDyad",
|
|
57
|
+
} ),
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
:::tip Multiple strategies?
|
|
65
|
+
It's fine to have multiple strategies of different name being set up like that. Just make sure name of property is different, URL-safe and provided as first argument to the generator method.
|
|
66
|
+
:::
|
|
67
|
+
|
|
68
|
+
The strategy in this example is named `saml`. Thus, the same name is given as first argument to the generator function, too. Its second argument provides configuration options for used to set up the strategy in detail.
|
|
69
|
+
|
|
70
|
+
:::warning Adapt to your IdP
|
|
71
|
+
The configuration in code example is for illustration, only. You need to adapt the settings to work with your IdP.
|
|
72
|
+
:::
|
|
73
|
+
|
|
74
|
+
* The **entryPoint** URL [https://idp.example.com/auth/realms/app-users/protocol/saml](https://idp.example.com/auth/realms/app-users/protocol/saml) is your IdP's URL for the realm of users meant to gain access to your application. In Keycloak this is called _Master SAML Processing URL_.
|
|
75
|
+
* The **issuer** `ci-saml-test` is the name of your application as registered with your IdP. In Keycloak that's the name of a registered _client_.
|
|
76
|
+
* The **signatureAlgorithm** must be changed due to `passport-saml` is defaulting to the more insecure `sha1` otherwise.
|
|
77
|
+
* The **cert**ificate - it's been significantly shortened in example above - is provided by your IdP for validating its signatures. It is required by `passport-saml` to validate signatures on responses provided by IdP.
|
|
78
|
+
|
|
79
|
+
In Keycloak the certificate to be used is found in your realm's settings, in tab _Keys_:
|
|
80
|
+
|
|
81
|
+

|
|
82
|
+
|
|
83
|
+
* The **callbackUrl** [http://localhost:3000/api/auth/login/saml](http://localhost:3000/api/auth/login/saml) is your application's absolute URL processing login requests returning from IdP via redirecting the user's browser after successful authentication. The path should be addressing the same endpoint triggering the authentication, thus it should be very similar to the example. It works due to [routing defaults](../api/routing.md).
|
|
84
|
+
|
|
85
|
+
No matter your eventual redirect URI, you have to configure it as a _valid redirect URI_ at your IdP.
|
|
86
|
+
|
|
87
|
+
* According to that, the **logoutCallbackUrl** [http://localhost:3000/api/auth/logout](http://localhost:3000/api/auth/logout) is your application's absolute URL processing logout requests after returning from IdP via redirecting the user's browser. Just like before, the path should be addressing the same endpoint triggering the logout. Don't forget to declare it a _valid redirect URI_ at your IdP, too.
|
|
88
|
+
|
|
89
|
+
## Restart and test
|
|
90
|
+
|
|
91
|
+
Restart your hitchy instance. Open browser at [http://localhot:3000/api/auth/current](http://localhot:3000/api/auth/current) and get a response like this one:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{"success":true,"authenticated":false}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The request has succeeded, but no user is authenticated currently.
|
|
98
|
+
|
|
99
|
+
Trigger authentication by opening URL [http://localhost:3000/api/auth/login/saml](http://localhost:3000/api/auth/login/saml) next. Last segment in this URL is referring to the strategy name you've picked when integrating it with the list of configured strategies.
|
|
100
|
+
|
|
101
|
+
This will redirect the browser to your IdP for prompting to log in:
|
|
102
|
+
|
|
103
|
+

|
|
104
|
+
|
|
105
|
+
After logging in there, your browser is instantly redirected back to the URL [http://localhost:3000/api/auth/login/saml](http://localhost:3000/api/auth/login/saml) as given in configuration above. This time it is succeeding with a response:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{"success":true,"authenticated":true}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
You are authenticated!
|
|
112
|
+
|
|
113
|
+
Return to the first URL requested above. It's providing a different set of information this time:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{"success":true,"authenticated":{"uuid":"d778afae-1234-4a58-a254-b56b1f36e914","name":"your-user","roles":[]}}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Try repeating this request if you like. Unless closing your browser, you stay authenticated.
|
|
120
|
+
|
|
121
|
+
If you happen to close the browser, just return to the login URL and - based on your IdP configuration - you will be re-authenticated instantly without being prompted for entering username and password again.
|
|
122
|
+
|
|
123
|
+
Next send a request to [http://localhost:3000/api/auth/logout](http://localhost:3000/api/auth/logout). It gets approved:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{"success":true}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Fetch current state of authentication at [https://localhost:3000/api/auth/current](https://localhost:3000/api/auth/current) once again.
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{"success":true,"authenticated":false}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Try to re-authenticate instantly at [http://localhost:3000/api/auth/login/saml](http://localhost:3000/api/auth/login/saml). It's going to prompt for username and password at your IdP again.
|
|
136
|
+
|
|
137
|
+
## How to welcome users?
|
|
138
|
+
|
|
139
|
+
Well, if you don't want your application's users to see the approval of logging in as raw JSON data, you simply have to replace the controller for route `GET /api/auth/login/saml` and make it provide any other response, e.g. some redirection to a welcome page of your application.
|
|
140
|
+
|
|
141
|
+
Adjust your application's file **config/routes.js** accordingly:
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
exports.routes = {
|
|
145
|
+
"GET /api/auth/login/saml": ( _, res ) => res.redirect( 301, "/welcome" ),
|
|
146
|
+
};
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
This is possible in a custom policy [processed after this plugin's policy](https://core.hitchy.org/internals/routing-basics.html#routing-slots), either. This could be your **config/policies.js** file:
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
exports.policies = {
|
|
153
|
+
"GET /api/auth/login/saml"( req, res, next ) {
|
|
154
|
+
if ( req.user ) {
|
|
155
|
+
res.redirect( 301, "/welcome" );
|
|
156
|
+
} else {
|
|
157
|
+
next();
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
```
|
package/docs/introduction.md
CHANGED
|
@@ -5,7 +5,7 @@ next: /guides/
|
|
|
5
5
|
|
|
6
6
|
# Introduction
|
|
7
7
|
|
|
8
|
-
**@hitchy/plugin-auth** is a plugin for the server-side framework [Hitchy](https://core.hitchy.org). It provides request handlers and policies for server-side request [authentication](#authentication) and [authorization](#authorization). It processes [authorization rules](api/config.md#config-auth-authorizations) in [runtime configuration](https://core.hitchy.org/api/hitchy.html#configuration). There are [ODM-based](https://odem.hitchy.org) [models](https://core.hitchy.org/internals/components.html#models) for managing [users](api/
|
|
8
|
+
**@hitchy/plugin-auth** is a plugin for the server-side framework [Hitchy](https://core.hitchy.org). It provides request handlers and policies for server-side request [authentication](#authentication) and [authorization](#authorization). It processes [authorization rules](api/config.md#config-auth-authorizations) in [runtime configuration](https://core.hitchy.org/api/hitchy.html#configuration). There are [ODM-based](https://odem.hitchy.org) [models](https://core.hitchy.org/internals/components.html#models) for managing [users](api/model/user.md), [roles](api/model/role) and their authorizations using [rules](api/model/authorization-rule.md) at runtime, too.
|
|
9
9
|
|
|
10
10
|
## Authentication
|
|
11
11
|
|
|
@@ -25,13 +25,13 @@ Server-side sessions keep track of authentication requests once they succeeded t
|
|
|
25
25
|
|
|
26
26
|
## Authorization
|
|
27
27
|
|
|
28
|
-
Authorization is the process of controlling an [identified
|
|
28
|
+
Authorization is the process of controlling an [identified user's](#authentication) permissions to access certain resources such as features, functions, data etc.
|
|
29
29
|
|
|
30
|
-
By intention, users have restricted access to your application. [Authorization rules](api/
|
|
30
|
+
By intention, users have restricted access to your application. [Authorization rules](api/model/authorization-rule.md) associate a named resource with one or more [users](api/model/user.md) and/or [roles](api/model/role.md) for granting or revoking access on those resources.
|
|
31
31
|
|
|
32
32
|
### Administrator account
|
|
33
33
|
|
|
34
|
-
On every start of application, the plugin checks if there is a [configurable](api/config.md#config-auth-admin) [user](api/
|
|
34
|
+
On every start of application, the plugin checks if there is a [configurable](api/config.md#config-auth-admin) [user](api/model/user.md) with full access to all routes and models and creates it if missing. Unless [configured](api/config.md#config-auth-admin) otherwise, that user's name is `admin` and its password is `nimda`.
|
|
35
35
|
|
|
36
36
|
### Resources
|
|
37
37
|
|
|
@@ -59,7 +59,7 @@ On naming related resources, consider future improvements relying on resources n
|
|
|
59
59
|
|
|
60
60
|
In this case, resulting resource names are `features.print`, `features.export.pdf` and `features.export.excel`.
|
|
61
61
|
|
|
62
|
-
[Authorization rules](api/
|
|
62
|
+
[Authorization rules](api/model/authorization-rule.md) are capable of granting/revoking access on either particular feature selected by its name as given above. In addition, authorization rules may address groups of features or all features altogether by using partial resource names such as `features` or `features.export`.
|
|
63
63
|
|
|
64
64
|
```json
|
|
65
65
|
{
|
package/index.js
CHANGED
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* (c) 2021 cepharum GmbH, Berlin, http://cepharum.de
|
|
3
|
-
*
|
|
4
|
-
* The MIT License (MIT)
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) 2021 cepharum GmbH
|
|
7
|
-
*
|
|
8
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
10
|
-
* in the Software without restriction, including without limitation the rights
|
|
11
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
13
|
-
* furnished to do so, subject to the following conditions:
|
|
14
|
-
*
|
|
15
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
16
|
-
* copies or substantial portions of the Software.
|
|
17
|
-
*
|
|
18
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
-
* SOFTWARE.
|
|
25
|
-
*
|
|
26
|
-
* @author: cepharum
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
1
|
"use strict";
|
|
30
2
|
|
|
31
3
|
module.exports = function( options, plugins ) {
|
|
@@ -33,17 +5,7 @@ module.exports = function( options, plugins ) {
|
|
|
33
5
|
|
|
34
6
|
const pluginApi = {
|
|
35
7
|
configure() {
|
|
36
|
-
|
|
37
|
-
api.config.auth = {};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const { auth } = api.config;
|
|
41
|
-
|
|
42
|
-
if ( !auth.strategies ) {
|
|
43
|
-
auth.strategies = {};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const { strategies } = auth;
|
|
8
|
+
const { strategies } = api.config.auth;
|
|
47
9
|
|
|
48
10
|
if ( !strategies.local ) {
|
|
49
11
|
strategies.local = api.runtime.services.AuthenticationStrategies.generateLocal();
|
|
@@ -55,15 +17,28 @@ module.exports = function( options, plugins ) {
|
|
|
55
17
|
*
|
|
56
18
|
* @returns {Promise<void>} promises plugin being set up
|
|
57
19
|
*/
|
|
58
|
-
initialize() {
|
|
59
|
-
const {
|
|
20
|
+
async initialize() {
|
|
21
|
+
const {
|
|
22
|
+
services: { AuthorizationTree, AuthenticationPassport, AuthManager },
|
|
23
|
+
} = api.runtime;
|
|
60
24
|
const { current } = AuthorizationTree;
|
|
61
25
|
|
|
62
|
-
|
|
63
26
|
AuthenticationPassport.integrateWithHitchy();
|
|
64
27
|
|
|
65
28
|
current.loadFromConfiguration( api.config.auth );
|
|
66
29
|
|
|
30
|
+
const roles = api.config.auth.roles;
|
|
31
|
+
|
|
32
|
+
if ( Array.isArray( roles ) && roles.length > 0 ) {
|
|
33
|
+
for ( const roleName of roles ) {
|
|
34
|
+
if ( typeof roleName === "string" && roleName && !/\s/.test( roleName.trim() ) ) {
|
|
35
|
+
await AuthManager.asRole( roleName.trim(), true ); // eslint-disable-line no-await-in-loop
|
|
36
|
+
} else {
|
|
37
|
+
throw new TypeError( "invalid name of role in runtime configuration" );
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
67
42
|
return Promise.all( [
|
|
68
43
|
AuthManager.createAdminUserIfMissing(),
|
|
69
44
|
current.loadFromDatabase(),
|
|
@@ -71,10 +46,13 @@ module.exports = function( options, plugins ) {
|
|
|
71
46
|
},
|
|
72
47
|
|
|
73
48
|
policies() {
|
|
74
|
-
const { config: { auth: { prefix
|
|
49
|
+
const { config: { auth: { prefix } } } = this;
|
|
75
50
|
|
|
76
51
|
const policies = {
|
|
77
|
-
"/":
|
|
52
|
+
"/": [
|
|
53
|
+
"authentication.initialize",
|
|
54
|
+
"authentication.handleBasicAuth",
|
|
55
|
+
],
|
|
78
56
|
};
|
|
79
57
|
|
|
80
58
|
return prefix === false ? policies : {
|
|
@@ -88,15 +66,17 @@ module.exports = function( options, plugins ) {
|
|
|
88
66
|
},
|
|
89
67
|
|
|
90
68
|
routes() {
|
|
91
|
-
const { config: { auth: { prefix
|
|
69
|
+
const { config: { auth: { prefix } } } = this;
|
|
92
70
|
|
|
93
71
|
return prefix === false ? {} : {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
72
|
+
after: {
|
|
73
|
+
[`POST ${prefix}/login/:strategy?`]: "user.authenticate",
|
|
74
|
+
[`GET ${prefix}/login/:strategy`]: "user.authenticate",
|
|
75
|
+
[`GET ${prefix}/current`]: "user.getCurrent",
|
|
76
|
+
[`GET ${prefix}/logout`]: "user.unauthenticate",
|
|
77
|
+
[`POST ${prefix}/password`]: ["user.changePassword"],
|
|
78
|
+
[`PATCH ${prefix}/password`]: ["user.changePassword"],
|
|
79
|
+
},
|
|
100
80
|
};
|
|
101
81
|
},
|
|
102
82
|
};
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hitchy/plugin-auth",
|
|
3
|
-
"version": "0.2
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "user authentication and authorization for Hitchy",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"test": "hitchy-pm session cookies odem --exec c8 -r text -r html mocha --recursive --exclude **/*.dist.js ./test/scripts --ui bdd",
|
|
9
8
|
"lint": "eslint .",
|
|
10
|
-
"
|
|
9
|
+
"test": "hitchy-pm session cookies odem --exec c8 -r text -r html mocha --recursive --exclude **/*.dist.js ./test/scripts --ui bdd",
|
|
10
|
+
"test:smoke": "hitchy-pm session cookies odem --exec hitchy --project test/project/testbed --plugins . --plugin . --debug",
|
|
11
11
|
"docs:dev": "vuepress dev docs",
|
|
12
12
|
"docs:build": "vuepress build docs"
|
|
13
13
|
},
|
|
@@ -21,23 +21,28 @@
|
|
|
21
21
|
"url": "https://gitlab.com/hitchy/plugin-auth/-/issues"
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://auth.hitchy.org",
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@hitchy/core": "^0.6.18"
|
|
26
|
+
},
|
|
24
27
|
"devDependencies": {
|
|
25
|
-
"@hitchy/server-dev-tools": "^0.2
|
|
28
|
+
"@hitchy/server-dev-tools": "^0.4.2",
|
|
26
29
|
"@hitchy/types": "^0.1.0",
|
|
27
|
-
"c8": "^7.
|
|
28
|
-
"eslint": "^
|
|
30
|
+
"c8": "^7.11.0",
|
|
31
|
+
"eslint": "^8.12.0",
|
|
29
32
|
"eslint-config-cepharum": "^1.0.12",
|
|
30
|
-
"eslint-plugin-promise": "^
|
|
31
|
-
"mocha": "^9.
|
|
33
|
+
"eslint-plugin-promise": "^6.0.0",
|
|
34
|
+
"mocha": "^9.2.2",
|
|
35
|
+
"openid-client": "^5.1.4",
|
|
36
|
+
"passport-saml": "^3.2.1",
|
|
32
37
|
"should": "^13.2.3",
|
|
33
38
|
"should-http": "^0.1.1",
|
|
34
|
-
"vuepress": "^1.
|
|
39
|
+
"vuepress": "^1.9.7"
|
|
35
40
|
},
|
|
36
41
|
"dependencies": {
|
|
37
|
-
"@hitchy/plugin-cookies": "^0.1.
|
|
38
|
-
"@hitchy/plugin-odem": "^0.5.
|
|
39
|
-
"@hitchy/plugin-session": "^0.1.
|
|
40
|
-
"passport": "^0.
|
|
42
|
+
"@hitchy/plugin-cookies": "^0.1.6",
|
|
43
|
+
"@hitchy/plugin-odem": "^0.5.10",
|
|
44
|
+
"@hitchy/plugin-session": "^0.1.10",
|
|
45
|
+
"passport": "^0.5.2",
|
|
41
46
|
"passport-local": "^1.0.0"
|
|
42
47
|
}
|
|
43
48
|
}
|