forked from outline/outline
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: authenticationProviders API endpoints (outline#1962)
- Loading branch information
Showing
19 changed files
with
671 additions
and
354 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// @flow | ||
import Router from "koa-router"; | ||
import allAuthenticationProviders from "../auth/providers"; | ||
import auth from "../middlewares/authentication"; | ||
import { AuthenticationProvider, Event } from "../models"; | ||
import policy from "../policies"; | ||
import { presentAuthenticationProvider, presentPolicies } from "../presenters"; | ||
|
||
const router = new Router(); | ||
const { authorize } = policy; | ||
|
||
router.post("authenticationProviders.info", auth(), async (ctx) => { | ||
const { id } = ctx.body; | ||
ctx.assertUuid(id, "id is required"); | ||
|
||
const user = ctx.state.user; | ||
const authenticationProvider = await AuthenticationProvider.findByPk(id); | ||
authorize(user, "read", authenticationProvider); | ||
|
||
ctx.body = { | ||
data: presentAuthenticationProvider(authenticationProvider), | ||
policies: presentPolicies(user, [authenticationProvider]), | ||
}; | ||
}); | ||
|
||
router.post("authenticationProviders.update", auth(), async (ctx) => { | ||
const { id, isEnabled } = ctx.body; | ||
ctx.assertUuid(id, "id is required"); | ||
ctx.assertPresent(isEnabled, "isEnabled is required"); | ||
|
||
const user = ctx.state.user; | ||
const authenticationProvider = await AuthenticationProvider.findByPk(id); | ||
authorize(user, "update", authenticationProvider); | ||
|
||
const enabled = !!isEnabled; | ||
if (enabled) { | ||
await authenticationProvider.enable(); | ||
} else { | ||
await authenticationProvider.disable(); | ||
} | ||
|
||
await Event.create({ | ||
name: "authenticationProviders.update", | ||
data: { enabled }, | ||
modelId: id, | ||
teamId: user.teamId, | ||
actorId: user.id, | ||
ip: ctx.request.ip, | ||
}); | ||
|
||
ctx.body = { | ||
data: presentAuthenticationProvider(authenticationProvider), | ||
policies: presentPolicies(user, [authenticationProvider]), | ||
}; | ||
}); | ||
|
||
router.post("authenticationProviders.list", auth(), async (ctx) => { | ||
const user = ctx.state.user; | ||
authorize(user, "read", user.team); | ||
|
||
const teamAuthenticationProviders = await user.team.getAuthenticationProviders(); | ||
const otherAuthenticationProviders = allAuthenticationProviders.filter( | ||
(p) => | ||
!teamAuthenticationProviders.find((t) => t.name === p.id) && | ||
p.enabled && | ||
// email auth is dealt with separetly right now, although it definitely | ||
// wants to be here in the future – we'll need to migrate more data though | ||
p.id !== "email" | ||
); | ||
|
||
ctx.body = { | ||
data: { | ||
authenticationProviders: [ | ||
...teamAuthenticationProviders.map(presentAuthenticationProvider), | ||
...otherAuthenticationProviders.map((p) => ({ | ||
name: p.id, | ||
isEnabled: false, | ||
isConnected: false, | ||
})), | ||
], | ||
}, | ||
}; | ||
}); | ||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
// @flow | ||
import TestServer from "fetch-test-server"; | ||
import uuid from "uuid"; | ||
import app from "../app"; | ||
import { buildUser, buildAdmin, buildTeam } from "../test/factories"; | ||
import { flushdb } from "../test/support"; | ||
|
||
const server = new TestServer(app.callback()); | ||
|
||
beforeEach(() => flushdb()); | ||
afterAll(() => server.close()); | ||
|
||
describe("#authenticationProviders.info", () => { | ||
it("should return auth provider", async () => { | ||
const team = await buildTeam(); | ||
const user = await buildUser({ teamId: team.id }); | ||
const authenticationProviders = await team.getAuthenticationProviders(); | ||
|
||
const res = await server.post("/api/authenticationProviders.info", { | ||
body: { | ||
id: authenticationProviders[0].id, | ||
token: user.getJwtToken(), | ||
}, | ||
}); | ||
const body = await res.json(); | ||
|
||
expect(res.status).toEqual(200); | ||
expect(body.data.name).toBe("slack"); | ||
expect(body.data.isEnabled).toBe(true); | ||
expect(body.data.isConnected).toBe(true); | ||
expect(body.policies[0].abilities.read).toBe(true); | ||
expect(body.policies[0].abilities.update).toBe(false); | ||
}); | ||
|
||
it("should require authorization", async () => { | ||
const team = await buildTeam(); | ||
const user = await buildUser(); | ||
const authenticationProviders = await team.getAuthenticationProviders(); | ||
|
||
const res = await server.post("/api/authenticationProviders.info", { | ||
body: { | ||
id: authenticationProviders[0].id, | ||
token: user.getJwtToken(), | ||
}, | ||
}); | ||
expect(res.status).toEqual(403); | ||
}); | ||
|
||
it("should require authentication", async () => { | ||
const team = await buildTeam(); | ||
const authenticationProviders = await team.getAuthenticationProviders(); | ||
|
||
const res = await server.post("/api/authenticationProviders.info", { | ||
body: { | ||
id: authenticationProviders[0].id, | ||
}, | ||
}); | ||
expect(res.status).toEqual(401); | ||
}); | ||
}); | ||
|
||
describe("#authenticationProviders.update", () => { | ||
it("should not allow admins to disable when last authentication provider", async () => { | ||
const team = await buildTeam(); | ||
const user = await buildAdmin({ teamId: team.id }); | ||
const authenticationProviders = await team.getAuthenticationProviders(); | ||
|
||
const res = await server.post("/api/authenticationProviders.update", { | ||
body: { | ||
id: authenticationProviders[0].id, | ||
isEnabled: false, | ||
token: user.getJwtToken(), | ||
}, | ||
}); | ||
|
||
expect(res.status).toEqual(400); | ||
}); | ||
|
||
it("should allow admins to disable", async () => { | ||
const team = await buildTeam(); | ||
const user = await buildAdmin({ teamId: team.id }); | ||
await team.createAuthenticationProvider({ | ||
name: "google", | ||
providerId: uuid.v4(), | ||
}); | ||
const authenticationProviders = await team.getAuthenticationProviders(); | ||
|
||
const res = await server.post("/api/authenticationProviders.update", { | ||
body: { | ||
id: authenticationProviders[0].id, | ||
isEnabled: false, | ||
token: user.getJwtToken(), | ||
}, | ||
}); | ||
const body = await res.json(); | ||
|
||
expect(res.status).toEqual(200); | ||
expect(body.data.name).toBe("slack"); | ||
expect(body.data.isEnabled).toBe(false); | ||
expect(body.data.isConnected).toBe(true); | ||
}); | ||
|
||
it("should require authorization", async () => { | ||
const team = await buildTeam(); | ||
const user = await buildUser({ teamId: team.id }); | ||
const authenticationProviders = await team.getAuthenticationProviders(); | ||
|
||
const res = await server.post("/api/authenticationProviders.update", { | ||
body: { | ||
id: authenticationProviders[0].id, | ||
isEnabled: false, | ||
token: user.getJwtToken(), | ||
}, | ||
}); | ||
expect(res.status).toEqual(403); | ||
}); | ||
|
||
it("should require authentication", async () => { | ||
const team = await buildTeam(); | ||
const authenticationProviders = await team.getAuthenticationProviders(); | ||
|
||
const res = await server.post("/api/authenticationProviders.update", { | ||
body: { | ||
id: authenticationProviders[0].id, | ||
isEnabled: false, | ||
}, | ||
}); | ||
expect(res.status).toEqual(401); | ||
}); | ||
}); | ||
|
||
describe("#authenticationProviders.list", () => { | ||
it("should return enabled and available auth providers", async () => { | ||
const team = await buildTeam(); | ||
const user = await buildUser({ teamId: team.id }); | ||
|
||
const res = await server.post("/api/authenticationProviders.list", { | ||
body: { token: user.getJwtToken() }, | ||
}); | ||
const body = await res.json(); | ||
|
||
expect(res.status).toEqual(200); | ||
expect(body.data.authenticationProviders.length).toBe(2); | ||
expect(body.data.authenticationProviders[0].name).toBe("slack"); | ||
expect(body.data.authenticationProviders[0].isEnabled).toBe(true); | ||
expect(body.data.authenticationProviders[0].isConnected).toBe(true); | ||
expect(body.data.authenticationProviders[1].name).toBe("google"); | ||
expect(body.data.authenticationProviders[1].isEnabled).toBe(false); | ||
expect(body.data.authenticationProviders[1].isConnected).toBe(false); | ||
}); | ||
|
||
it("should require authentication", async () => { | ||
const res = await server.post("/api/authenticationProviders.list"); | ||
expect(res.status).toEqual(401); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.