Skip to content

Commit e00a437

Browse files
authored
feat: authenticationProviders API endpoints (outline#1962)
1 parent 626c94e commit e00a437

19 files changed

+671
-354
lines changed

ARCHITECTURE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ Interested in more documentation on the API routes? Check out the [API documenta
3434
server
3535
├── api - All API routes are contained within here
3636
│ └── middlewares - Koa middlewares specific to the API
37-
├── auth - Authentication providers, in the form of passport.js strategies
37+
├── auth - Authentication logic
38+
│ └── providers - Authentication providers export passport.js strategies and config
3839
├── commands - We are gradually moving to the command pattern for new write logic
3940
├── config - Database configuration
4041
├── emails - Transactional email templates

server/api/auth.js

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,14 @@
11
// @flow
2-
import path from "path";
32
import Router from "koa-router";
43
import { find } from "lodash";
54
import { parseDomain, isCustomSubdomain } from "../../shared/utils/domains";
6-
import { signin } from "../../shared/utils/routeHelpers";
5+
import providers from "../auth/providers";
76
import auth from "../middlewares/authentication";
87
import { Team } from "../models";
98
import { presentUser, presentTeam, presentPolicies } from "../presenters";
109
import { isCustomDomain } from "../utils/domains";
11-
import { requireDirectory } from "../utils/fs";
1210

1311
const router = new Router();
14-
let providers = [];
15-
16-
requireDirectory(path.join(__dirname, "..", "auth")).forEach(
17-
([{ config }, id]) => {
18-
if (config && config.enabled) {
19-
providers.push({
20-
id,
21-
name: config.name,
22-
authUrl: signin(id),
23-
});
24-
}
25-
}
26-
);
2712

2813
function filterProviders(team) {
2914
return providers

server/api/authenticationProviders.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// @flow
2+
import Router from "koa-router";
3+
import allAuthenticationProviders from "../auth/providers";
4+
import auth from "../middlewares/authentication";
5+
import { AuthenticationProvider, Event } from "../models";
6+
import policy from "../policies";
7+
import { presentAuthenticationProvider, presentPolicies } from "../presenters";
8+
9+
const router = new Router();
10+
const { authorize } = policy;
11+
12+
router.post("authenticationProviders.info", auth(), async (ctx) => {
13+
const { id } = ctx.body;
14+
ctx.assertUuid(id, "id is required");
15+
16+
const user = ctx.state.user;
17+
const authenticationProvider = await AuthenticationProvider.findByPk(id);
18+
authorize(user, "read", authenticationProvider);
19+
20+
ctx.body = {
21+
data: presentAuthenticationProvider(authenticationProvider),
22+
policies: presentPolicies(user, [authenticationProvider]),
23+
};
24+
});
25+
26+
router.post("authenticationProviders.update", auth(), async (ctx) => {
27+
const { id, isEnabled } = ctx.body;
28+
ctx.assertUuid(id, "id is required");
29+
ctx.assertPresent(isEnabled, "isEnabled is required");
30+
31+
const user = ctx.state.user;
32+
const authenticationProvider = await AuthenticationProvider.findByPk(id);
33+
authorize(user, "update", authenticationProvider);
34+
35+
const enabled = !!isEnabled;
36+
if (enabled) {
37+
await authenticationProvider.enable();
38+
} else {
39+
await authenticationProvider.disable();
40+
}
41+
42+
await Event.create({
43+
name: "authenticationProviders.update",
44+
data: { enabled },
45+
modelId: id,
46+
teamId: user.teamId,
47+
actorId: user.id,
48+
ip: ctx.request.ip,
49+
});
50+
51+
ctx.body = {
52+
data: presentAuthenticationProvider(authenticationProvider),
53+
policies: presentPolicies(user, [authenticationProvider]),
54+
};
55+
});
56+
57+
router.post("authenticationProviders.list", auth(), async (ctx) => {
58+
const user = ctx.state.user;
59+
authorize(user, "read", user.team);
60+
61+
const teamAuthenticationProviders = await user.team.getAuthenticationProviders();
62+
const otherAuthenticationProviders = allAuthenticationProviders.filter(
63+
(p) =>
64+
!teamAuthenticationProviders.find((t) => t.name === p.id) &&
65+
p.enabled &&
66+
// email auth is dealt with separetly right now, although it definitely
67+
// wants to be here in the future – we'll need to migrate more data though
68+
p.id !== "email"
69+
);
70+
71+
ctx.body = {
72+
data: {
73+
authenticationProviders: [
74+
...teamAuthenticationProviders.map(presentAuthenticationProvider),
75+
...otherAuthenticationProviders.map((p) => ({
76+
name: p.id,
77+
isEnabled: false,
78+
isConnected: false,
79+
})),
80+
],
81+
},
82+
};
83+
});
84+
85+
export default router;
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// @flow
2+
import TestServer from "fetch-test-server";
3+
import uuid from "uuid";
4+
import app from "../app";
5+
import { buildUser, buildAdmin, buildTeam } from "../test/factories";
6+
import { flushdb } from "../test/support";
7+
8+
const server = new TestServer(app.callback());
9+
10+
beforeEach(() => flushdb());
11+
afterAll(() => server.close());
12+
13+
describe("#authenticationProviders.info", () => {
14+
it("should return auth provider", async () => {
15+
const team = await buildTeam();
16+
const user = await buildUser({ teamId: team.id });
17+
const authenticationProviders = await team.getAuthenticationProviders();
18+
19+
const res = await server.post("/api/authenticationProviders.info", {
20+
body: {
21+
id: authenticationProviders[0].id,
22+
token: user.getJwtToken(),
23+
},
24+
});
25+
const body = await res.json();
26+
27+
expect(res.status).toEqual(200);
28+
expect(body.data.name).toBe("slack");
29+
expect(body.data.isEnabled).toBe(true);
30+
expect(body.data.isConnected).toBe(true);
31+
expect(body.policies[0].abilities.read).toBe(true);
32+
expect(body.policies[0].abilities.update).toBe(false);
33+
});
34+
35+
it("should require authorization", async () => {
36+
const team = await buildTeam();
37+
const user = await buildUser();
38+
const authenticationProviders = await team.getAuthenticationProviders();
39+
40+
const res = await server.post("/api/authenticationProviders.info", {
41+
body: {
42+
id: authenticationProviders[0].id,
43+
token: user.getJwtToken(),
44+
},
45+
});
46+
expect(res.status).toEqual(403);
47+
});
48+
49+
it("should require authentication", async () => {
50+
const team = await buildTeam();
51+
const authenticationProviders = await team.getAuthenticationProviders();
52+
53+
const res = await server.post("/api/authenticationProviders.info", {
54+
body: {
55+
id: authenticationProviders[0].id,
56+
},
57+
});
58+
expect(res.status).toEqual(401);
59+
});
60+
});
61+
62+
describe("#authenticationProviders.update", () => {
63+
it("should not allow admins to disable when last authentication provider", async () => {
64+
const team = await buildTeam();
65+
const user = await buildAdmin({ teamId: team.id });
66+
const authenticationProviders = await team.getAuthenticationProviders();
67+
68+
const res = await server.post("/api/authenticationProviders.update", {
69+
body: {
70+
id: authenticationProviders[0].id,
71+
isEnabled: false,
72+
token: user.getJwtToken(),
73+
},
74+
});
75+
76+
expect(res.status).toEqual(400);
77+
});
78+
79+
it("should allow admins to disable", async () => {
80+
const team = await buildTeam();
81+
const user = await buildAdmin({ teamId: team.id });
82+
await team.createAuthenticationProvider({
83+
name: "google",
84+
providerId: uuid.v4(),
85+
});
86+
const authenticationProviders = await team.getAuthenticationProviders();
87+
88+
const res = await server.post("/api/authenticationProviders.update", {
89+
body: {
90+
id: authenticationProviders[0].id,
91+
isEnabled: false,
92+
token: user.getJwtToken(),
93+
},
94+
});
95+
const body = await res.json();
96+
97+
expect(res.status).toEqual(200);
98+
expect(body.data.name).toBe("slack");
99+
expect(body.data.isEnabled).toBe(false);
100+
expect(body.data.isConnected).toBe(true);
101+
});
102+
103+
it("should require authorization", async () => {
104+
const team = await buildTeam();
105+
const user = await buildUser({ teamId: team.id });
106+
const authenticationProviders = await team.getAuthenticationProviders();
107+
108+
const res = await server.post("/api/authenticationProviders.update", {
109+
body: {
110+
id: authenticationProviders[0].id,
111+
isEnabled: false,
112+
token: user.getJwtToken(),
113+
},
114+
});
115+
expect(res.status).toEqual(403);
116+
});
117+
118+
it("should require authentication", async () => {
119+
const team = await buildTeam();
120+
const authenticationProviders = await team.getAuthenticationProviders();
121+
122+
const res = await server.post("/api/authenticationProviders.update", {
123+
body: {
124+
id: authenticationProviders[0].id,
125+
isEnabled: false,
126+
},
127+
});
128+
expect(res.status).toEqual(401);
129+
});
130+
});
131+
132+
describe("#authenticationProviders.list", () => {
133+
it("should return enabled and available auth providers", async () => {
134+
const team = await buildTeam();
135+
const user = await buildUser({ teamId: team.id });
136+
137+
const res = await server.post("/api/authenticationProviders.list", {
138+
body: { token: user.getJwtToken() },
139+
});
140+
const body = await res.json();
141+
142+
expect(res.status).toEqual(200);
143+
expect(body.data.authenticationProviders.length).toBe(2);
144+
expect(body.data.authenticationProviders[0].name).toBe("slack");
145+
expect(body.data.authenticationProviders[0].isEnabled).toBe(true);
146+
expect(body.data.authenticationProviders[0].isConnected).toBe(true);
147+
expect(body.data.authenticationProviders[1].name).toBe("google");
148+
expect(body.data.authenticationProviders[1].isEnabled).toBe(false);
149+
expect(body.data.authenticationProviders[1].isConnected).toBe(false);
150+
});
151+
152+
it("should require authentication", async () => {
153+
const res = await server.post("/api/authenticationProviders.list");
154+
expect(res.status).toEqual(401);
155+
});
156+
});

server/api/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import validation from "../middlewares/validation";
1010
import apiKeys from "./apiKeys";
1111
import attachments from "./attachments";
1212
import auth from "./auth";
13+
import authenticationProviders from "./authenticationProviders";
1314
import collections from "./collections";
1415
import documents from "./documents";
1516
import events from "./events";
@@ -45,6 +46,7 @@ api.use(editor());
4546

4647
// routes
4748
router.use("/", auth.routes());
49+
router.use("/", authenticationProviders.routes());
4850
router.use("/", events.routes());
4951
router.use("/", users.routes());
5052
router.use("/", collections.routes());

0 commit comments

Comments
 (0)