feat(auth): implement auth-then-redirect in a global way@@ -1,9 +1,23 @@
// 3rd-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import LoginView, { LoginViewProps } from "../../views/auth/LoginView";
const getLoginView: ReqHandler = (request, reply) => {
+ const { after_login_goto } =
+ request.query as AppRouteParams[AppRoute.AUTH_LOGIN]["querystring"];
+
+ if (after_login_goto != null && after_login_goto.trim().startsWith("/")) {
+ // ! TODO(security):
+ // ! - [x] check that path is not an external url (avoid open redirect attack)
+ // ! - [ ] check that path belongs to a registered route (how?)
+ // ! - [ ] **do not** honour requests when both conditions ^ are not met
+
+ console.log("after_login_goto:", after_login_goto);
+ request.session.data.auth_redirect_to = after_login_goto;
+ }
+
const reqHandler = reply.makeRequestHandler(request, reply);
return reqHandler<LoginViewProps>(LoginView.name, {});
};
@@ -1,11 +1,14 @@
// 3rd-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import LoginView, { LoginViewProps } from "../../views/auth/LoginView";
import { makeAuthService } from "../../services/auth";
const postLoginAction: ReqHandler = async (request, reply) => {
+ const { email_address: emailAddress, password } =
+ request.body as AppRouteParams[AppRoute.AUTH_LOGIN_ACTION]["body"];
+
const authService = makeAuthService({
cryptoService: request.cryptoService,
request,
@@ -13,9 +16,6 @@ const postLoginAction: ReqHandler = async (request, reply) => {
const reqHandler = reply.makeRequestHandler(request, reply);
- const { email_address: emailAddress, password } =
- request.body as AppRoutesParams[AppRoute.AUTH_LOGIN_ACTION]["body"];
-
const initialValues = { emailAddress };
if (request.validationError != null) {
@@ -30,14 +30,14 @@ const postLoginAction: ReqHandler = async (request, reply) => {
if (emailAddress.trim() === "") {
return reqHandler<LoginViewProps>(LoginView.name, {
errorMessage: "Please provide a non-empty email address.",
- initialValues: { emailAddress },
+ initialValues,
});
}
if (password.trim() === "") {
return reqHandler<LoginViewProps>(LoginView.name, {
errorMessage: "Please provide a non-empty password.",
- initialValues: { emailAddress },
+ initialValues,
});
}
@@ -45,7 +45,7 @@ const postLoginAction: ReqHandler = async (request, reply) => {
return reqHandler<LoginViewProps>(LoginView.name, {
errorMessage:
"Invalid credentials. Please verify your input and try again.",
- initialValues: { emailAddress },
+ initialValues,
});
}
@@ -58,19 +58,30 @@ const postLoginAction: ReqHandler = async (request, reply) => {
return reqHandler<LoginViewProps>(LoginView.name, {
errorMessage:
"Invalid credentials. Please verify your input and try again.",
- initialValues: { emailAddress },
+ initialValues,
});
}
- const { avatarUri, role, id: userId, username } = user;
+ // Set the session data such as the user is authenticated.
request.session.data.authenticated = true;
- request.session.data.curr_user_avatar_uri = avatarUri;
- request.session.data.curr_user_role = role;
- request.session.data.curr_user_uid = userId;
- request.session.data.curr_user_username = username;
+ request.session.data.curr_user_avatar_uri = user.avatarUri;
+ request.session.data.curr_user_role = user.role;
+ request.session.data.curr_user_uid = user.id;
+ request.session.data.curr_user_username = user.username;
- console.log(`Logged user with id: ${userId}`);
+ // Log success
+ console.log(`User (id: ${user.id}, role: ${user.role}) just logged!`);
+
+ // In case a redirect to url is set in session, go there.
+ const authRedirectToUrl = request.session.data.auth_redirect_to;
+ if (authRedirectToUrl != null) {
+ console.log("Will redirect back to before-auth uri.", authRedirectToUrl);
+ request.session.data.auth_redirect_to = null; // prevent replay attack
+ reply.redirect(302, authRedirectToUrl);
+ return reply;
+ }
+ // Everything is ok, redirect to user dashboard
reply.redirect(302, request.namedViewsPathMap[AppRoute.USER_DASHBOARD]);
return reply;
};
@@ -1,7 +1,7 @@
// 3rd-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import RegisterView, { RegisterViewProps } from "../../views/auth/RegisterView";
import { makeAuthService } from "../../services/auth";
@@ -17,7 +17,7 @@ const postRegisterAction: ReqHandler = async (request, reply) => {
email_address: emailAddress,
username,
password,
- } = request.body as AppRoutesParams[AppRoute.AUTH_REGISTER_ACTION]["body"];
+ } = request.body as AppRouteParams[AppRoute.AUTH_REGISTER_ACTION]["body"];
const initialValues = { emailAddress, username };
@@ -8,7 +8,7 @@ import {
User,
} from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
// app views
@@ -19,7 +19,7 @@ import OrganizationDetailsView, {
const getOrganizationDetailsView: ReqHandler = async (request, reply) => {
const { curr_user_uid } = request.session.data;
const { orgSlug } =
- request.params as AppRoutesParams[AppRoute.ORGANIZATION_DETAILS]["params"];
+ request.params as AppRouteParams[AppRoute.ORGANIZATION_DETAILS]["params"];
const orgService = makeOrganizationService({ request });
const organization = (await orgService.getOrganizationBySlug(orgSlug, {
@@ -3,7 +3,7 @@ import type { ReqHandler } from "@ethicdevs/react-monolith";
// generated via script[generate:prisma]
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import { Const } from "../../const";
// app services
import { makeOrganizationService } from "../../services/organization";
@@ -19,7 +19,7 @@ import RepositoryDetailsView, {
const getRepositoryBrowserView: ReqHandler = async (request, reply) => {
const params =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_BROWSER]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_BROWSER]["params"];
const { orgSlug, repoSlug, currentRef: currRef } = params;
const path = params["*"] || "";
const currentRef = currRef || Const.DEFAULT_HEAD_REF;
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
@@ -14,7 +14,7 @@ import RepositoryCommitsLogView, {
const getRepositoryCommitsLogView: ReqHandler = async (request, reply) => {
const { orgSlug, repoSlug, currentRef } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_COMMITS_LOG]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_COMMITS_LOG]["params"];
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
@@ -2,7 +2,7 @@
import { ReqHandler } from "@ethicdevs/react-monolith";
// generated via script[generate:prisma]
import { ResourceVisibility } from "@prisma/client";
-import { AppRoute, AppRoutesParams } from "app/routes.defs";
+import { AppRoute, AppRouteParams } from "app/routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
@@ -14,7 +14,7 @@ import RepositoryCompareView, {
const getRepositoryCompareView: ReqHandler = async (request, reply) => {
const { orgSlug, repoSlug, refA, refB } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_COMPARE]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_COMPARE]["params"];
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
@@ -1,7 +1,7 @@
// 1st-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import { Const } from "../../const";
// app services
import { makeOrganizationService } from "../../services/organization";
@@ -19,7 +19,7 @@ import {
const getRepositoryDetailsView: ReqHandler = async (request, reply) => {
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_DETAILS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_DETAILS]["params"];
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
@@ -1,7 +1,7 @@
// 1st-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
@@ -11,7 +11,7 @@ import RepositoryForkView, {
RepositoryForkViewProps,
} from "../../views/repository/RepositoryForkView";
-type RouteParams = AppRoutesParams[AppRoute.REPOSITORY_FORK];
+type RouteParams = AppRouteParams[AppRoute.REPOSITORY_FORK];
const getRepositoryForkView: ReqHandler = async (request, reply) => {
if (
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
@@ -14,7 +14,7 @@ import RepositoryShowObjectView, {
const getRepositoryShowObjectView: ReqHandler = async (request, reply) => {
const { orgSlug, repoSlug, objectId } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_SHOW_OBJECT]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_SHOW_OBJECT]["params"];
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
@@ -3,7 +3,7 @@ import { join, resolve } from "node:path";
// 1st-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import { Env } from "../../env";
// app services
import { makeRepositoryService } from "../../services/repository";
@@ -49,7 +49,7 @@ const postRepositoryCreateAction: ReqHandler = async (request, reply) => {
repo_short_description: shortDescription,
repo_visibility: visibility,
repo_website_url: websiteUrl,
- } = body as AppRoutesParams[AppRoute.REPOSITORY_CREATE_ACTION]["body"];
+ } = body as AppRouteParams[AppRoute.REPOSITORY_CREATE_ACTION]["body"];
const newRepo = await repoService.createRepository({
parentOrgSlug, // TODO: Validate it exists first.
@@ -3,7 +3,7 @@ import { join, resolve } from "node:path";
// 1st-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import { Env } from "../../env";
// app services
import { makeOrganizationService } from "../../services/organization";
@@ -31,14 +31,14 @@ const postRepositoryForkAction: ReqHandler = async (request, reply) => {
const { body, params, validationError } = request;
const { orgSlug: sourceOrgSlug, repoSlug: sourceRepoSlug } =
- params as AppRoutesParams[AppRoute.REPOSITORY_FORK_ACTION]["params"];
+ params as AppRouteParams[AppRoute.REPOSITORY_FORK_ACTION]["params"];
const {
target_org_slug: targetOrgSlug,
target_repo_slug: targetRepoSlug,
target_repo_display_name: targetRepoDisplayName,
target_repo_visibility: targetRepoVisibility,
- } = body as AppRoutesParams[AppRoute.REPOSITORY_FORK_ACTION]["body"];
+ } = body as AppRouteParams[AppRoute.REPOSITORY_FORK_ACTION]["body"];
const sourceParentOrg = await organizationService.getOrganizationBySlug(
sourceOrgSlug
@@ -77,7 +77,7 @@ const postRepositoryForkAction: ReqHandler = async (request, reply) => {
sourceRepo,
errorMessage,
initialValues:
- body as AppRoutesParams[AppRoute.REPOSITORY_FORK_ACTION]["body"],
+ body as AppRouteParams[AppRoute.REPOSITORY_FORK_ACTION]["body"],
});
}
@@ -112,7 +112,7 @@ const postRepositoryForkAction: ReqHandler = async (request, reply) => {
sourceRepo,
errorMessage: (err as Error).message,
initialValues:
- body as AppRoutesParams[AppRoute.REPOSITORY_FORK_ACTION]["body"],
+ body as AppRouteParams[AppRoute.REPOSITORY_FORK_ACTION]["body"],
});
}
};
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -18,7 +18,7 @@ const getRepositoryPullRequestCloseAction: ReqHandler = async (
reply
) => {
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -18,7 +18,7 @@ const getRepositoryPullRequestCommentCreateAction: ReqHandler = async (
reply
) => {
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -18,7 +18,7 @@ const getRepositoryPullRequestCommentDeleteAction: ReqHandler = async (
reply
) => {
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -18,7 +18,7 @@ const getRepositoryPullRequestCommentUpdateAction: ReqHandler = async (
reply
) => {
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -1,9 +1,11 @@
// 1st-party
+import { Organization } from "@prisma/client";
import { ReqHandler } from "@ethicdevs/react-monolith";
// app
import type { RepositoryWithParentAndForkedFromRepos } from "../../types";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import { Const } from "../../const";
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { buildRouteLink } from "../../utils/shared";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
@@ -18,25 +20,41 @@ import {
import RepositoryPullRequestCreateView, {
RepositoryPullRequestCreateViewProps,
} from "../../views/repositoryPullRequests/RepositoryPullRequestCreateView";
-import { Organization } from "@prisma/client";
const getRepositoryPullRequestCreateView: ReqHandler = async (
request,
reply
) => {
+ const { from_branch } =
+ request.query as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]["querystring"];
+
+ const { orgSlug, repoSlug } =
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]["params"];
+
if (
request.session.data.authenticated === false ||
request.session.data.curr_user_uid == null
) {
- reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
+ const getBackHereLink = buildRouteLink(
+ AppRoute.REPOSITORY_PULL_REQUEST_CREATE,
+ {
+ orgSlug,
+ repoSlug,
+ }
+ );
+
+ const redirectUrl = [
+ request.namedViewsPathMap[AppRoute.AUTH_LOGIN],
+ `?after_login_goto=${getBackHereLink}`,
+ ];
+
+ console.log("getBackHereLink:", getBackHereLink);
+ console.log("redirectUrl:", redirectUrl);
+
+ reply.redirect(302, redirectUrl.join(""));
return reply;
}
- const { from_branch } =
- request.query as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]["querystring"];
- const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]["params"];
-
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
const usersService = makeUsersService({ request });
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -18,7 +18,7 @@ const getRepositoryPullRequestDeleteAction: ReqHandler = async (
reply
) => {
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -3,7 +3,7 @@ import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility, User } from "@prisma/client";
// app
import type { RepositoryFileDiff } from "../../types";
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -19,7 +19,7 @@ const getRepositoryPullRequestDetailsView: ReqHandler = async (
reply
) => {
const { orgSlug, repoSlug, pullUid } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_DETAILS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUEST_DETAILS]["params"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -18,7 +18,7 @@ const getRepositoryPullRequestUpdateAction: ReqHandler = async (
reply
) => {
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -15,7 +15,7 @@ import RepositoryPullRequestsView, {
const getRepositoryPullRequestsView: ReqHandler = async (request, reply) => {
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
+ request.params as AppRouteParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -2,7 +2,7 @@
import { ReqHandler } from "@ethicdevs/react-monolith";
// app
import type { RepositoryFileDiff } from "../../types";
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import { buildRouteLink } from "../../utils/shared";
// app islands
import {
@@ -34,7 +34,7 @@ const postRepositoryPullRequestCreateAction: ReqHandler = async (
}
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[CurrentRoute]["params"];
+ request.params as AppRouteParams[CurrentRoute]["params"];
const {
description,
summary,
@@ -46,7 +46,7 @@ const postRepositoryPullRequestCreateAction: ReqHandler = async (
target_parent_org_slug: targetParentOrgSlug,
target_repository_dest_branch: targetRepoDestBranch,
target_repository_slug: targetRepoSlug,
- } = request.body as AppRoutesParams[CurrentRoute]["body"];
+ } = request.body as AppRouteParams[CurrentRoute]["body"];
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
@@ -3,7 +3,7 @@ import type { ReqHandler } from "@ethicdevs/react-monolith";
import { ResourceVisibility } from "@prisma/client";
// app
import type { RepositoryFileDiff } from "../../types";
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makePullRequestService } from "../../services/pullRequest";
@@ -21,9 +21,9 @@ const postRepositoryPullRequestMergeAction: ReqHandler = async (
reply
) => {
const { orgSlug, repoSlug, pullUid } =
- request.params as AppRoutesParams[CurrentRoute]["params"];
+ request.params as AppRouteParams[CurrentRoute]["params"];
// const { merge_message, merge_summary } =
- // request.body as AppRoutesParams[CurrentRoute]["body"];
+ // request.body as AppRouteParams[CurrentRoute]["body"];
const orgService = makeOrganizationService({ request });
const prService = makePullRequestService({ request });
@@ -5,7 +5,7 @@ import Prism from "prismjs";
import { parse as parseHtmlToJson, TextNode, RealNode } from "himalaya";
// app
import type { AppThemeScheme } from "../../types";
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import { escapeHtmlCode } from "../../utils/shared/escapeHtmlCode";
Prism.languages.prisma = Prism.languages.extend("javascript", {
@@ -85,13 +85,13 @@ const getNodesRecursive = (
const highlightCodeAction: ReqHandler = async (request, reply) => {
const { outputFormat = "html" } =
- request.params as AppRoutesParams[AppRoute.SYNTAX_HIGHLIGHT_HIGHLIGHT_CODE_ACTION]["params"];
+ request.params as AppRouteParams[AppRoute.SYNTAX_HIGHLIGHT_HIGHLIGHT_CODE_ACTION]["params"];
const {
code,
language,
theme_scheme: themeScheme,
- } = request.body as AppRoutesParams[AppRoute.SYNTAX_HIGHLIGHT_HIGHLIGHT_CODE_ACTION]["body"];
+ } = request.body as AppRouteParams[AppRoute.SYNTAX_HIGHLIGHT_HIGHLIGHT_CODE_ACTION]["body"];
if (request.validationError != null) {
if (outputFormat === "html") {
@@ -1,12 +1,12 @@
// 1st-party
import { ReqHandler } from "@ethicdevs/react-monolith";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
const setThemeSchemeAction: ReqHandler = async (request, reply) => {
const { referer } = request.headers;
const { themeScheme: desiredScheme } =
- request.params as AppRoutesParams[AppRoute.THEME_SET_SCHEME_ACTION]["params"];
+ request.params as AppRouteParams[AppRoute.THEME_SET_SCHEME_ACTION]["params"];
reply.setCookie("theme_scheme", desiredScheme === "light" ? "light" : "dark");
return reply.redirect(302, referer || "/");
@@ -2,7 +2,7 @@
import type { ReqHandler } from "@ethicdevs/react-monolith";
import { User } from "@prisma/client";
// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRouteParams } from "../../routes.defs";
import { makeUsersService } from "../../services/user";
// app views
import UserDetailsView, {
@@ -11,7 +11,7 @@ import UserDetailsView, {
const getUserDetailsView: ReqHandler = async (request, reply) => {
const { username } =
- request.params as AppRoutesParams[AppRoute.USER_DETAILS]["params"];
+ request.params as AppRouteParams[AppRoute.USER_DETAILS]["params"];
const { curr_user_uid } = request.session.data;
@@ -94,7 +94,7 @@ export const AppRoutePaths: Record<AppRoute, string> = {
[AppRoute.USER_DETAILS]: "/@:username",
};
-export interface AppRoutesParams extends IRouteParams {
+export interface AppRouteParams extends IRouteParams {
[AppRoute.HOME]: undefined;
[AppRoute.THEME_SET_SCHEME_ACTION]: {
params: {
@@ -109,7 +109,11 @@ export interface AppRoutesParams extends IRouteParams {
password: string;
};
};
- [AppRoute.AUTH_LOGIN]: undefined;
+ [AppRoute.AUTH_LOGIN]: {
+ querystring: {
+ after_login_goto?: string;
+ };
+ };
[AppRoute.AUTH_LOGIN_ACTION]: {
body: {
email_address: string;
@@ -326,7 +330,7 @@ export interface AppRoutesParams extends IRouteParams {
};
}
-export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
+export const AppRouteSchemas: Record<AppRoute, undefined | FastifySchema> = {
[AppRoute.HOME]: undefined,
[AppRoute.THEME_SET_SCHEME_ACTION]: {
params: {
@@ -354,7 +358,16 @@ export const AppRoutesSchemas: Record<AppRoute, undefined | FastifySchema> = {
},
},
},
- [AppRoute.AUTH_LOGIN]: undefined,
+ [AppRoute.AUTH_LOGIN]: {
+ querystring: {
+ type: "object",
+ required: [],
+ additionalProperties: false,
+ properties: {
+ after_login_goto: { type: "string" },
+ },
+ },
+ },
[AppRoute.AUTH_LOGIN_ACTION]: {
body: {
type: "object",
@@ -3,7 +3,7 @@ import { AppRouter, AppRouterGroup, Router } from "@ethicdevs/react-monolith";
// 3rd-party
import React from "react";
// app
-import { AppRoute, AppRoutePaths, AppRoutesSchemas } from "./routes.defs";
+import { AppRoute, AppRoutePaths, AppRouteSchemas } from "./routes.defs";
import { authenticatedOrLogin, guestOrRedirect } from "./utils/server";
// app controllers
import {
@@ -31,7 +31,7 @@ const RootAppRouter: AppRouter = () => {
name={AppRoute.THEME_SET_SCHEME_ACTION}
method={"GET"}
path={AppRoutePaths[AppRoute.THEME_SET_SCHEME_ACTION]}
- schema={AppRoutesSchemas[AppRoute.THEME_SET_SCHEME_ACTION]}
+ schema={AppRouteSchemas[AppRoute.THEME_SET_SCHEME_ACTION]}
handler={ThemeController.setThemeSchemeAction}
/>
{/* --- */}
@@ -55,7 +55,7 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.AUTH_REGISTER_ACTION]}
preHandler={guestOrDashboardRedirect}
- schema={AppRoutesSchemas[AppRoute.AUTH_REGISTER_ACTION]}
+ schema={AppRouteSchemas[AppRoute.AUTH_REGISTER_ACTION]}
handler={AuthController.postRegisterAction}
/>
{/* --- */}
@@ -64,6 +64,7 @@ const RootAppRouter: AppRouter = () => {
method={"GET"}
path={AppRoutePaths[AppRoute.AUTH_LOGIN]}
preHandler={guestOrDashboardRedirect}
+ schema={AppRouteSchemas[AppRoute.AUTH_LOGIN]}
handler={AuthController.getLoginView}
/>
<Router.Route
@@ -71,7 +72,7 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.AUTH_LOGIN_ACTION]}
preHandler={guestOrDashboardRedirect}
- schema={AppRoutesSchemas[AppRoute.AUTH_LOGIN_ACTION]}
+ schema={AppRouteSchemas[AppRoute.AUTH_LOGIN_ACTION]}
handler={AuthController.postLoginAction}
/>
<Router.Route
@@ -79,7 +80,7 @@ const RootAppRouter: AppRouter = () => {
method={"GET"}
path={AppRoutePaths[AppRoute.AUTH_LOGOUT_ACTION]}
preHandler={loggedOrLoginRedirect}
- schema={AppRoutesSchemas[AppRoute.AUTH_LOGOUT_ACTION]}
+ schema={AppRouteSchemas[AppRoute.AUTH_LOGOUT_ACTION]}
handler={AuthController.getLogoutAction}
/>
{/* --- */}
@@ -101,7 +102,7 @@ const RootAppRouter: AppRouter = () => {
name={AppRoute.ORGANIZATION_DETAILS}
method={"GET"}
path={AppRoutePaths[AppRoute.ORGANIZATION_DETAILS]}
- schema={AppRoutesSchemas[AppRoute.ORGANIZATION_DETAILS]}
+ schema={AppRouteSchemas[AppRoute.ORGANIZATION_DETAILS]}
handler={OrganizationController.getOrganizationDetailsView}
/>
{/* --- */}
@@ -109,28 +110,28 @@ const RootAppRouter: AppRouter = () => {
name={AppRoute.REPOSITORY_BROWSER}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_BROWSER]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_BROWSER]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_BROWSER]}
handler={RepositoryController.getRepositoryBrowserView}
/>
<Router.Route
name={AppRoute.REPOSITORY_BROWSER_WITH_PATH}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_BROWSER_WITH_PATH]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_BROWSER]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_BROWSER]}
handler={RepositoryController.getRepositoryBrowserView}
/>
<Router.Route
name={AppRoute.REPOSITORY_COMMITS_LOG}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_COMMITS_LOG]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_COMMITS_LOG]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_COMMITS_LOG]}
handler={RepositoryController.getRepositoryCommitsLogView}
/>
<Router.Route
name={AppRoute.REPOSITORY_COMPARE}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_COMPARE]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_COMPARE]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_COMPARE]}
handler={RepositoryController.getRepositoryCompareView}
/>
<Router.Route
@@ -145,21 +146,21 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.REPOSITORY_CREATE_ACTION]}
preHandler={loggedOrLoginRedirect}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_CREATE_ACTION]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_CREATE_ACTION]}
handler={RepositoryController.postRepositoryCreateAction}
/>
<Router.Route
name={AppRoute.REPOSITORY_DETAILS}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_DETAILS]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_DETAILS]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_DETAILS]}
handler={RepositoryController.getRepositoryDetailsView}
/>
<Router.Route
name={AppRoute.REPOSITORY_DETAILS_WITH_TRAILING_SLASH}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_DETAILS_WITH_TRAILING_SLASH]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_DETAILS]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_DETAILS]}
handler={RepositoryController.getRepositoryDetailsView}
/>
<Router.Route
@@ -187,7 +188,7 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.REPOSITORY_PULL_REQUEST_CLOSE_ACTION]}
schema={
- AppRoutesSchemas[AppRoute.REPOSITORY_PULL_REQUEST_CLOSE_ACTION]
+ AppRouteSchemas[AppRoute.REPOSITORY_PULL_REQUEST_CLOSE_ACTION]
}
preHandler={loggedOrLoginRedirect}
handler={
@@ -203,7 +204,7 @@ const RootAppRouter: AppRouter = () => {
]
}
schema={
- AppRoutesSchemas[
+ AppRouteSchemas[
AppRoute.REPOSITORY_PULL_REQUEST_COMMENT_CREATE_ACTION
]
}
@@ -221,7 +222,7 @@ const RootAppRouter: AppRouter = () => {
]
}
schema={
- AppRoutesSchemas[
+ AppRouteSchemas[
AppRoute.REPOSITORY_PULL_REQUEST_COMMENT_DELETE_ACTION
]
}
@@ -239,7 +240,7 @@ const RootAppRouter: AppRouter = () => {
]
}
schema={
- AppRoutesSchemas[
+ AppRouteSchemas[
AppRoute.REPOSITORY_PULL_REQUEST_COMMENT_UPDATE_ACTION
]
}
@@ -252,7 +253,7 @@ const RootAppRouter: AppRouter = () => {
name={AppRoute.REPOSITORY_PULL_REQUEST_CREATE}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]}
preHandler={loggedOrLoginRedirect}
handler={
RepositoryPullRequestsController.getRepositoryPullRequestCreateView
@@ -263,7 +264,7 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION]}
schema={
- AppRoutesSchemas[AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION]
+ AppRouteSchemas[AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION]
}
preHandler={loggedOrLoginRedirect}
handler={
@@ -275,7 +276,7 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.REPOSITORY_PULL_REQUEST_DELETE_ACTION]}
schema={
- AppRoutesSchemas[AppRoute.REPOSITORY_PULL_REQUEST_DELETE_ACTION]
+ AppRouteSchemas[AppRoute.REPOSITORY_PULL_REQUEST_DELETE_ACTION]
}
preHandler={loggedOrLoginRedirect}
handler={
@@ -286,7 +287,7 @@ const RootAppRouter: AppRouter = () => {
name={AppRoute.REPOSITORY_PULL_REQUEST_DETAILS}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_PULL_REQUEST_DETAILS]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_PULL_REQUEST_DETAILS]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_PULL_REQUEST_DETAILS]}
preHandler={loggedOrLoginRedirect}
handler={
RepositoryPullRequestsController.getRepositoryPullRequestDetailsView
@@ -297,7 +298,7 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.REPOSITORY_PULL_REQUEST_MERGE_ACTION]}
schema={
- AppRoutesSchemas[AppRoute.REPOSITORY_PULL_REQUEST_MERGE_ACTION]
+ AppRouteSchemas[AppRoute.REPOSITORY_PULL_REQUEST_MERGE_ACTION]
}
preHandler={loggedOrLoginRedirect}
handler={
@@ -309,7 +310,7 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.REPOSITORY_PULL_REQUEST_UPDATE_ACTION]}
schema={
- AppRoutesSchemas[AppRoute.REPOSITORY_PULL_REQUEST_UPDATE_ACTION]
+ AppRouteSchemas[AppRoute.REPOSITORY_PULL_REQUEST_UPDATE_ACTION]
}
preHandler={loggedOrLoginRedirect}
handler={
@@ -320,7 +321,7 @@ const RootAppRouter: AppRouter = () => {
name={AppRoute.REPOSITORY_PULL_REQUESTS}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_PULL_REQUESTS]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_PULL_REQUESTS]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_PULL_REQUESTS]}
handler={
RepositoryPullRequestsController.getRepositoryPullRequestsView
}
@@ -329,7 +330,7 @@ const RootAppRouter: AppRouter = () => {
name={AppRoute.REPOSITORY_SHOW_OBJECT}
method={"GET"}
path={AppRoutePaths[AppRoute.REPOSITORY_SHOW_OBJECT]}
- schema={AppRoutesSchemas[AppRoute.REPOSITORY_SHOW_OBJECT]}
+ schema={AppRouteSchemas[AppRoute.REPOSITORY_SHOW_OBJECT]}
handler={RepositoryController.getRepositoryShowObjectView}
/>
{/* --- */}
@@ -338,7 +339,7 @@ const RootAppRouter: AppRouter = () => {
method={"POST"}
path={AppRoutePaths[AppRoute.SYNTAX_HIGHLIGHT_HIGHLIGHT_CODE_ACTION]}
schema={
- AppRoutesSchemas[AppRoute.SYNTAX_HIGHLIGHT_HIGHLIGHT_CODE_ACTION]
+ AppRouteSchemas[AppRoute.SYNTAX_HIGHLIGHT_HIGHLIGHT_CODE_ACTION]
}
handler={SyntaxHighlightController.highlightCodeAction}
/>
@@ -293,6 +293,7 @@ async function main(): Promise<AppServer> {
initialSession: {
sessionId: null,
authenticated: false,
+ auth_redirect_to: null,
curr_user_avatar_uri: null,
curr_user_uid: null,
curr_user_username: null,
@@ -14,6 +14,7 @@ export type WithThemeSchemeProp = {
export interface AppSessionData extends Prisma.JsonObject {
sessionId: null | string;
authenticated: boolean;
+ auth_redirect_to: null | string;
flash_message: null | string;
flash_message_shown_once: boolean;
curr_user_avatar_uri: null | string;
@@ -5,6 +5,27 @@ import { AppRoute } from "../../routes.defs";
export const authenticatedOrLogin =
(): preHandlerHookHandler => async (request, reply) => {
if (request.session.data.authenticated === false) {
- reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
+ const parts = request.routerPath.split("/");
+ const params = request.params as Record<string, unknown>;
+ const redirectGotoUri = parts.map((part) => {
+ if (part.trim() === "") return part;
+ if (part.startsWith(":")) {
+ const paramKey = part.substring(1);
+ const param = params[paramKey];
+ if (param != null) return param;
+ return part;
+ }
+ return part;
+ });
+
+ const redirectAuthUri = [
+ request.namedViewsPathMap[AppRoute.AUTH_LOGIN],
+ `?after_login_goto=${redirectGotoUri.join("/")}`,
+ ].join("");
+
+ console.log("redirectTo:", redirectAuthUri);
+
+ // reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
+ reply.redirect(302, redirectAuthUri);
}
};
@@ -1,4 +1,4 @@
-import { AppRoute, AppRoutePaths, AppRoutesParams } from "../../routes.defs";
+import { AppRoute, AppRoutePaths, AppRouteParams } from "../../routes.defs";
interface BuildLinkOptions {
// @default true
@@ -7,8 +7,8 @@ interface BuildLinkOptions {
export default function buildRouteLink<P extends AppRoute>(
route: P,
- routeParams: "params" extends keyof AppRoutesParams[P]
- ? AppRoutesParams[P]["params"]
+ routeParams: "params" extends keyof AppRouteParams[P]
+ ? AppRouteParams[P]["params"]
: {} | null,
options?: BuildLinkOptions
): typeof AppRoutePaths[P] {
@@ -18,8 +18,8 @@ export default function buildRouteLink<P extends AppRoute>(
export function buildPathLink<P extends AppRoute>(
path: string,
- routeParams: "params" extends keyof AppRoutesParams[P]
- ? AppRoutesParams[P]["params"]
+ routeParams: "params" extends keyof AppRouteParams[P]
+ ? AppRouteParams[P]["params"]
: {} | null,
options?: BuildLinkOptions
): string {