Skip to content

Commit 705f761

Browse files
committed
Initial OpenID integration with Cloudron
1 parent f985911 commit 705f761

11 files changed

+1037
-276
lines changed

CloudronManifest.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"httpPort": 3000,
1313
"addons": {
1414
"mongodb": {},
15-
"ldap": {},
15+
"oidc": {
16+
"loginRedirectUri": "/api/callback"
17+
},
1618
"localstorage": {}
1719
},
1820
"tags": ["notes", "bookmarks", "todo", "ideas", "feed", "markdown"],

app.js

+81-5
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@
44

55
require('supererror')({ splatchError: true });
66

7-
var PORT = process.env.PORT || 3000;
8-
var BIND_ADDRESS = process.env.BIND_ADDRESS || '0.0.0.0';
7+
const PORT = process.env.VITE_DEV_PORT || process.env.PORT || 3000;
8+
const BIND_ADDRESS = process.env.BIND_ADDRESS || '0.0.0.0';
99

1010
if (!process.env.CLOUDRON_APP_ORIGIN) {
1111
console.log('No CLOUDRON_APP_ORIGIN env var set. Falling back to http://localhost');
1212
}
1313

14+
const APP_ORIGIN = process.env.CLOUDRON_APP_ORIGIN || `http://localhost:${PORT}`;
15+
1416
var express = require('express'),
1517
json = require('body-parser').json,
1618
config = require('./src/config.js'),
1719
cors = require('cors'),
1820
session = require('express-session'),
1921
MongoStore = require('connect-mongo'),
2022
multer = require('multer'),
23+
oidc = require('express-openid-connect'),
2124
routes = require('./src/routes.js'),
2225
lastmile = require('connect-lastmile'),
2326
logic = require('./src/logic.js'),
@@ -35,6 +38,15 @@ var memoryUpload = multer({ storage: multer.memoryStorage({}) }).any();
3538

3639
router.del = router.delete;
3740

41+
router.get ('/api/login', function (req, res) {
42+
res.oidc.login({
43+
returnTo: '/',
44+
authorizationParams: {
45+
redirect_uri: `${APP_ORIGIN}/api/callback`,
46+
}
47+
});
48+
});
49+
3850
router.post('/api/things', routes.auth, routes.add);
3951
router.get ('/api/things', routes.auth, routes.getAll);
4052
router.get ('/api/things/:id', routes.auth, routes.get);
@@ -52,9 +64,6 @@ router.get ('/api/settings', routes.auth, routes.settingsGet);
5264
router.get ('/api/export', routes.auth, routes.exportThings);
5365
router.post('/api/import', routes.auth, diskUpload, routes.importThings);
5466

55-
router.post('/api/login', routes.login);
56-
router.get ('/api/logout', routes.auth, routes.logout);
57-
5867
router.get ('/api/profile', routes.auth, routes.profile);
5968

6069
// public apis
@@ -90,6 +99,73 @@ app.use(session({
9099
cookie: { sameSite: 'strict' },
91100
store: MongoStore.create({ mongoUrl: config.databaseUrl })
92101
}));
102+
103+
if (process.env.CLOUDRON_OIDC_ISSUER) {
104+
app.use(oidc.auth({
105+
issuerBaseURL: process.env.CLOUDRON_OIDC_ISSUER,
106+
baseURL: APP_ORIGIN,
107+
clientID: process.env.CLOUDRON_OIDC_CLIENT_ID,
108+
clientSecret: process.env.CLOUDRON_OIDC_CLIENT_SECRET,
109+
secret: 'FIXME this secret',
110+
authorizationParams: {
111+
response_type: 'code',
112+
scope: 'openid profile email'
113+
},
114+
authRequired: false,
115+
routes: {
116+
callback: '/api/callback',
117+
login: false,
118+
logout: '/api/logout'
119+
}
120+
}));
121+
} else {
122+
// mock oidc
123+
console.log('CLOUDRON_OIDC_ISSUER is not set, using mock OpenID for development');
124+
125+
app.use((req, res, next) => {
126+
res.oidc = {
127+
login(options) {
128+
res.writeHead(200, { 'Content-Type': 'text/html' })
129+
res.write(require('fs').readFileSync(__dirname + '/oidc_develop_user_select.html', 'utf8').replaceAll('REDIRECT_URI', options.authorizationParams.redirect_uri));
130+
res.end()
131+
}
132+
};
133+
134+
req.oidc = {
135+
user: {},
136+
isAuthenticated() {
137+
return !!req.session.username;
138+
}
139+
};
140+
141+
if (req.session.username) {
142+
req.oidc.user = {
143+
sub: req.session.username,
144+
family_name: 'Cloudron',
145+
given_name: req.session.username.toUpperCase(),
146+
locale: 'en-US',
147+
name: 'Cloudron ' + req.session.username.toUpperCase(),
148+
preferred_username: req.session.username,
149+
email: req.session.username + '@cloudron.local',
150+
email_verified: true
151+
};
152+
}
153+
154+
next();
155+
});
156+
157+
app.use('/api/callback', (req, res) => {
158+
console.log(req.query)
159+
req.session.username = req.query.username;
160+
res.redirect(`http://localhost:${PORT}/`);
161+
});
162+
163+
app.use('/api/logout', (req, res) => {
164+
req.session.username = null;
165+
res.status(200).send({});
166+
});
167+
}
168+
93169
app.use(router);
94170
app.use(lastmile());
95171

frontend/templates/view-login.html

+1-11
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,7 @@
66
<div class="row">
77
<div class="col-md-4 col-md-offset-4 white-boxes shadow">
88
<h3>Login</h3>
9-
<form @submit.prevent="login()">
10-
<div class="form-group" :class="{ 'has-error': error }">
11-
<label class="control-label">Username</label>
12-
<input type="text" class="form-control" id="inputUsername" v-model="username" :disabled="busy" required autofocus/>
13-
</div>
14-
<div class="form-group" :class="{ 'has-error': error }">
15-
<label class="control-label">Password</label>
16-
<input type="password" class="form-control" v-model="password" :disabled="busy" required/>
17-
</div>
18-
<button type="submit" class="btn btn-success" :disabled="busy"><i class="spinner alt" v-show="busy"></i> Login</button>
19-
</form>
9+
<a href="/api/login" class="btn btn-success">Login with Cloudron</a>
2010
</div>
2111
</div>
2212
</div>

frontend/templates/view-login.js

+2-33
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,12 @@
44

55
Vue.component('view-login', {
66
template: '#view-login-template',
7-
data: function () {
8-
return {
9-
busy: false,
10-
error: false,
11-
username: '',
12-
password: '',
13-
users: []
14-
};
7+
data() {
8+
return {};
159
},
1610
methods: {
1711
login: function () {
18-
var that = this;
1912

20-
that.busy = true;
21-
that.error = false;
22-
23-
that.$root.Core.session.login(that.username, that.password, function (error, user) {
24-
that.busy = false;
25-
26-
if (error) {
27-
that.error = true;
28-
that.username = '';
29-
that.password = '';
30-
31-
Vue.nextTick(function () { $('#inputUsername').focus(); });
32-
33-
// print unexpected errors
34-
if (error.status !== 401) console.error('Login failed:', error);
35-
36-
return;
37-
}
38-
39-
that.error = false;
40-
41-
// TODO should be a signal/slot
42-
that.$root.main();
43-
});
4413
}
4514
},
4615
ready: function () {

localdevelopment

+1-8
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ CONTAINER_NAME="mongo-server-meemo"
88
OUT=`docker inspect ${CONTAINER_NAME}` || true
99
if [[ "${OUT}" = "[]" ]]; then
1010
echo "Starting ${CONTAINER_NAME}..."
11-
docker run --name ${CONTAINER_NAME} -d mongo:4.2.12-bionic
11+
docker run --name ${CONTAINER_NAME} -d mongo:5.0.20
1212
else
1313
echo "${CONTAINER_NAME} already running. If you want to start fresh, run 'docker rm --force ${CONTAINER_NAME}'"
1414
fi
@@ -20,13 +20,6 @@ done
2020

2121
export MONGODB_IP=`docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ${CONTAINER_NAME}`
2222

23-
echo ""
24-
echo "Adding developer test user:"
25-
echo "username: test"
26-
echo "password: test"
27-
echo ""
28-
./admin user-add --username test --password test --display-name "Test User" || true
29-
3023
export CLOUDRON_MONGODB_URL="mongodb://${MONGODB_IP}:27017/meemo"
3124
export CLOUDRON_APP_ORIGIN="http://localhost:3000"
3225

oidc_develop_user_select.html

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
<body>
3+
<h1>OpenID mock</h1>
4+
<p>Choose with which user to continue:</p>
5+
<ul>
6+
<li><a href="REDIRECT_URI?username=admin">Admin</a></li>
7+
<li><a href="REDIRECT_URI?username=helena">Helena</a></li>
8+
</ul>
9+
</body>
10+
</html>

0 commit comments

Comments
 (0)