feat(repository): make private repositories actually private (not reachable when not logged as a member of parent org)@@ -14,6 +14,7 @@ import RepositoryBrowserView, {
import RepositoryDetailsView, {
RepositoryDetailsViewProps,
} from "../../views/repository/RepositoryDetailsView";
+import { ResourceVisibility } from "@prisma/client";
const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
const params =
@@ -34,10 +35,20 @@ const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
const repo = await repoService.getRepository(orgSlug, repoSlug);
- if (repo == null) {
+ if (parentOrg == null || repo == null) {
return reply.status(404).callNotFound();
}
+ if (repo.visibility === ResourceVisibility.PRIVATE) {
+ if (currentUser == null) {
+ return reply.status(404).callNotFound();
+ } else if (
+ (await repoService.canUserAccessRepository(currentUser, repo)) === false
+ ) {
+ return reply.status(404).callNotFound();
+ }
+ }
+
const reqHandler = reply.makeRequestHandler(request, reply);
if (path.endsWith("/")) {
@@ -1,10 +1,12 @@
// 1st-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
+import { ResourceVisibility } from "@prisma/client";
// app
import { AppRoute, AppRoutesParams } from "../../routes";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
+import { makeUsersService } from "../../services/user";
// app views
import RepositoryCommitsLogView, {
RepositoryCommitsLogViewProps,
@@ -16,14 +18,31 @@ const getRepositoryCommitsLogView: ReqHandler = async (request, reply) => {
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
+ const usersService = makeUsersService({ request });
+
+ const currentUser =
+ request.session.data.authenticated &&
+ request.session.data.curr_user_uid != null
+ ? await usersService.getUserById(request.session.data.curr_user_uid)
+ : null;
const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
const repo = await repoService.getRepository(orgSlug, repoSlug);
- if (repo == null) {
+ if (parentOrg == null || repo == null) {
return reply.status(404).callNotFound();
}
+ if (repo.visibility === ResourceVisibility.PRIVATE) {
+ if (currentUser == null) {
+ return reply.status(404).callNotFound();
+ } else if (
+ (await repoService.canUserAccessRepository(currentUser, repo)) === false
+ ) {
+ return reply.status(404).callNotFound();
+ }
+ }
+
const history = await repoService.getRepositoryCommitLog(repo);
const reqHandler = reply.makeRequestHandler(request, reply);
@@ -1,7 +1,7 @@
// 1st-party
import { ReqHandler } from "@ethicdevs/react-monolith";
// generated via script[generate:prisma]
-import { User } from "@prisma/client";
+import { ResourceVisibility } from "@prisma/client";
import { AppRoute, AppRoutesParams } from "app/routes";
// app services
import { makeOrganizationService } from "../../services/organization";
@@ -13,13 +13,18 @@ import RepositoryCompareView, {
} from "../../views/repository/RepositoryCompareView";
const getRepositoryCompareView: ReqHandler = async (request, reply) => {
- const { curr_user_uid } = request.session.data;
const { orgSlug, repoSlug, refA, refB } =
request.params as AppRoutesParams[AppRoute.REPOSITORY_COMPARE]["params"];
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
- const userService = makeUsersService({ request });
+ const usersService = makeUsersService({ request });
+
+ const currentUser =
+ request.session.data.authenticated &&
+ request.session.data.curr_user_uid != null
+ ? await usersService.getUserById(request.session.data.curr_user_uid)
+ : null;
const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
const repo = await repoService.getRepository(orgSlug, repoSlug);
@@ -28,10 +33,14 @@ const getRepositoryCompareView: ReqHandler = async (request, reply) => {
return reply.status(404).callNotFound();
}
- let currentUser: null | User = null;
-
- if (curr_user_uid != null) {
- currentUser = await userService.getUserById(curr_user_uid);
+ if (repo.visibility === ResourceVisibility.PRIVATE) {
+ if (currentUser == null) {
+ return reply.status(404).callNotFound();
+ } else if (
+ (await repoService.canUserAccessRepository(currentUser, repo)) === false
+ ) {
+ return reply.status(404).callNotFound();
+ }
}
const filesDiffs = await repoService.getRepositoryRefDiff(repo, refA, refB);
@@ -11,6 +11,11 @@ import { makeUsersService } from "../../services/user";
import RepositoryDetailsView, {
RepositoryDetailsViewProps,
} from "../../views/repository/RepositoryDetailsView";
+import {
+ Organization,
+ OrganizationMembership,
+ ResourceVisibility,
+} from "@prisma/client";
const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
const { orgSlug, repoSlug } =
@@ -26,15 +31,30 @@ const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
? await usersService.getUserById(request.session.data.curr_user_uid)
: null;
- const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
const path = "/";
const ref = "HEAD";
+
+ const parentOrg = (await orgService.getOrganizationBySlug(orgSlug, {
+ memberships: true,
+ })) as Organization & {
+ memberships: OrganizationMembership[];
+ };
const repo = await repoService.getRepository(orgSlug, repoSlug);
- if (repo == null) {
+ if (parentOrg == null || repo == null) {
return reply.status(404).callNotFound();
}
+ if (repo.visibility === ResourceVisibility.PRIVATE) {
+ if (currentUser == null) {
+ return reply.status(404).callNotFound();
+ } else if (
+ (await repoService.canUserAccessRepository(currentUser, repo)) === false
+ ) {
+ return reply.status(404).callNotFound();
+ }
+ }
+
const readmeFiles = await repoService.isFileInRepositoryPath(
repo,
"",
@@ -0,0 +1,33 @@
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import type { Repository, User } from "@prisma/client";
+// service
+import type { RepositoryServiceDeps } from "./types";
+
+const makeCanUserAccessRepository: ServiceMethodFactory<
+ RepositoryServiceDeps,
+ [User, Repository],
+ Promise<boolean>
+> = ({ request }) => {
+ return async (user, repo) => {
+ const parentOrg = await request.prisma.organization.findUnique({
+ include: {
+ memberships: true,
+ },
+ where: {
+ id: repo.organizationId,
+ },
+ });
+
+ return (
+ parentOrg != null &&
+ (user.id === parentOrg.ownerId ||
+ parentOrg.memberships.find(
+ (m) => user.id === m.userId && m.revokedAt == null
+ ) != null)
+ );
+ };
+};
+
+export default makeCanUserAccessRepository;
@@ -3,6 +3,7 @@ import { makeService } from "@ethicdevs/react-monolith";
// app
import type { RepositoryServiceAPI, RepositoryServiceDeps } from "./types";
// service methods
+import { default as makeCanUserAccessRepository } from "./canUserAccessRepository";
import { default as makeCreateRepository } from "./createRepository";
import { default as makeGetRepository } from "./getRepository";
import { default as makeGetRepositoryBranches } from "./getRepositoryBranches";
@@ -22,6 +23,7 @@ export const makeRepositoryService = makeService<
RepositoryServiceAPI,
RepositoryServiceDeps
>({
+ canUserAccessRepository: makeCanUserAccessRepository,
createRepository: makeCreateRepository,
getRepository: makeGetRepository,
getRepositoryBranches: makeGetRepositoryBranches,
@@ -5,7 +5,7 @@ import type { ServiceApiContract } from "@ethicdevs/react-monolith";
// 3rd-party
import type { FastifyRequest } from "fastify";
// generated via script[generate:prisma]
-import type { Organization, Repository } from "@prisma/client";
+import type { Organization, Repository, User } from "@prisma/client";
// app
import type {
RepositoryFile,
@@ -31,6 +31,7 @@ export interface CreateRepositoryDTO {
}
export interface RepositoryServiceAPI extends ServiceApiContract {
+ canUserAccessRepository(user: User, repo: Repository): Promise<boolean>;
createRepository(dto: CreateRepositoryDTO): Promise<Repository>;
getRepository(orgSlug: string, repoSlug: string): Promise<Repository | null>;
getRepositoryBranches(repository: Repository): Promise<string[]>;