Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions backend/server/src/handler/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,32 @@ pub async fn google_callback(
Ok((jar.add(cookie), Redirect::to(redirect_url.as_str())))
}

pub async fn logout(
State(state): State<AppState>,
jar: CookieJar
) -> Result<impl IntoResponse, ChaosError> {
let domain = if state.is_dev_env {
"localhost"
} else {
"devsoc.app"
};

let empty_cookie= Cookie::build(("auth_token", ""))
.http_only(true) // Prevent JavaScript access
.expires(Expiration::DateTime(OffsetDateTime::now_utc() + time::Duration::days(5))) // Set an expiration time of 5 days, TODO: read from env?
.secure(!state.is_dev_env) // Send only over HTTPS, comment out for testing
.domain(domain)
.path("/");

let redirect = if state.is_dev_env {
"http://localhost:3000"
} else {
"https://chaos.devsoc.app"
};

Ok((jar.remove(empty_cookie), Redirect::to(redirect)))
}

pub struct DevLoginHandler;
impl DevLoginHandler {

Expand Down
1 change: 1 addition & 0 deletions backend/server/src/handler/organisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ impl OrganisationHandler {
admin.user_id,
request_body.email,
state.email_credentials.clone(),
state.is_dev_env,
&mut state.snowflake_generator,
&mut transaction.tx,
)
Expand Down
3 changes: 2 additions & 1 deletion backend/server/src/models/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::handler::answer::AnswerHandler;
use crate::handler::application::ApplicationHandler;
use crate::handler::auth::{google_callback, google_auth_init, DevLoginHandler};
use crate::handler::auth::{DevLoginHandler, google_auth_init, google_callback, logout};
use crate::handler::campaign::CampaignHandler;
use crate::handler::email_template::EmailTemplateHandler;
use crate::handler::offer::OfferHandler;
Expand Down Expand Up @@ -201,6 +201,7 @@ pub async fn app() -> Result<(Router, AppState), ChaosError> {
let router = Router::new()
.route("/", get(|| async { "Join DevSoc! https://devsoc.app/" }))
.route("/auth/google", get(google_auth_init))
.route("/auth/logout", get(logout))
.route("/api/auth/callback/google", get(google_callback))
.route("/api/v1/dev/super_admin_login", get(DevLoginHandler::dev_super_admin_login))
.route("/api/v1/dev/org_admin_login", get(DevLoginHandler::dev_org_admin_login))
Expand Down
4 changes: 2 additions & 2 deletions backend/server/src/models/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ impl Application {

coalesce(
to_jsonb(
array_agg(
array_agg(DISTINCT
jsonb_build_object(
'id', ar.id,
'rater_id', reviewer.id,
Expand All @@ -690,7 +690,7 @@ impl Application {
WHERE crc.campaign_id = a.campaign_id
),
'updated_at', ar.updated_at
) ORDER BY ar.updated_at DESC
)
) FILTER (WHERE ar.id IS NOT NULL)
),
'[]'::jsonb
Expand Down
21 changes: 13 additions & 8 deletions backend/server/src/models/organisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,7 @@ impl Organisation {
inviting_user_id: i64,
email: String,
email_credentials: EmailCredentials,
is_dev_env: bool,
snowflake_generator: &mut SnowflakeIdGenerator,
transaction: &mut Transaction<'_, Postgres>,
) -> Result<String, ChaosError> {
Expand Down Expand Up @@ -830,14 +831,18 @@ impl Organisation {
.execute(transaction.deref_mut())
.await?;

ChaosEmail::send_message(
None,
email,
"You have been invited to join an organisation on Chaos".to_string(),
format!("You have been invited to join an organisation on Chaos. Please use the following link to accept the invite: https://chaos.devsoc.app/dashboard/invite/{code}").to_string(),
email_credentials
)
.await?;
if is_dev_env {
println!("Invite code for {email}: {code}")
} else {
ChaosEmail::send_message(
None,
email,
"You have been invited to join an organisation on Chaos".to_string(),
format!("You have been invited to join an organisation on Chaos. Please use the following link to accept the invite: https://chaos.devsoc.app/dashboard/invite/{code}").to_string(),
email_credentials
)
.await?;
}

return Ok(code);
}
Expand Down
2 changes: 2 additions & 0 deletions backend/server/src/models/rating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ impl Rating {
"
INSERT INTO application_ratings (id, application_id, rater_id, comment)
VALUES ($1, $2, $3, $4)
ON CONFLICT (application_id, rater_id)
DO UPDATE SET comment = $4, updated_at = NOW()
",
rating_id,
application_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import Link from "next/link";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { User } from "@/models/user";
import { LogOut } from "lucide-react";

type Props = {
code: string;
currentUser?: User;
dict: any;
};

export default function InviteClient({ code, dict }: Props) {
export default function InviteClient({ code, currentUser, dict }: Props) {
const router = useRouter();

const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");
Expand Down Expand Up @@ -46,18 +49,17 @@ export default function InviteClient({ code, dict }: Props) {
const inviteInvalid = !invite || invite.expired || invite.used || status === "success";

return (
<div className="max-w-xl mx-auto p-6 flex flex-col gap-4">
<div className="max-w-3xl mx-auto p-6 flex flex-col gap-4">
<h1 className="text-2xl font-bold">{dict.dashboard.invite.title}</h1>
<p className="text-sm text-muted-foreground">
<p className="text-lg">
{invite?.organisation_name}{" "}
{invite
? dict.dashboard.invite.invited_by
: dict?.common?.loading ?? "Loading..."}
{dict.dashboard.invite.invited_by}
</p>
{/* Show the email the invite was sent to */}
{invite && (
<p className="text-sm text-muted-foreground">
{dict.dashboard.invite.sent_to}: <span className="font-medium">{invite.email}</span>
<p className="text-lg">
{dict.dashboard.invite.sent_to}: <span className="font-medium">{invite.email}</span>.
{(!currentUser || currentUser.email !== invite.email) && dict.dashboard.invite.sent_to_note}
</p>
)}
{/* Show the expired message if the invite has expired */}
Expand All @@ -74,25 +76,41 @@ export default function InviteClient({ code, dict }: Props) {
{message}
</p>
)}
<div className="flex flex-col gap-2">
<Link href={`/login?to=/dashboard/invite/${code}`} className="w-full">
<Button variant="outline" className="w-full">
{dict.dashboard.invite.login_cta}
{
currentUser && currentUser.email === invite?.email && (
<Button onClick={handleAccept} disabled={status === "loading" || inviteInvalid} className="w-full">
{status === "loading" ? "Loading..." : dict.dashboard.invite.accept_cta}
</Button>
</Link>
<Button
onClick={handleAccept}
disabled={status === "loading" || inviteInvalid}
className="w-full"
>
{status === "loading" ? "Loading..." : dict.dashboard.invite.accept_cta}
</Button>
{/* Show the wrong account message if the account is not invited */}
<p className="text-xs text-muted-foreground">
{dict.dashboard.invite.wrong_account}
</p>
</div>
</div>
)
}
{/* Show the wrong account message if the account is not invited */}
{
currentUser && currentUser.email !== invite?.email && (
<>
<p className="text-xl">
{dict.dashboard.invite.wrong_account}
</p>
<Link href={`${process.env.NEXT_PUBLIC_API_BASE_URL || "https://chaos-api.devsoc.app"}/auth/logout`} className="w-full">
<Button variant="outline" className="w-full">
<LogOut />
{dict.common.logout}
</Button>
</Link>
</>
)
}
{
!currentUser && (
<div className="flex flex-col gap-2">
<Link href={`/login?to=/dashboard/invite/${code}`} className="w-full">
<Button variant="outline" className="w-full">
{dict.dashboard.invite.login_cta}
</Button>
</Link>
</div>
)
}
</div >
);
}

Expand Down
10 changes: 9 additions & 1 deletion frontend-nextjs/src/app/[lang]/dashboard/invite/[code]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { getDictionary } from "@/app/[lang]/dictionaries";
import { getInvite } from "@/models/invite";
import { HydrationBoundary, QueryClient, dehydrate } from "@tanstack/react-query";
import InviteClient from "./invite-client";
import { getCurrentUser } from "@/lib";
import { User } from "@/models/user";

export default async function Page({ params }: { params: Promise<{ lang: string; code: string }> }) {
const { lang, code } = await params;
Expand All @@ -13,9 +15,15 @@ export default async function Page({ params }: { params: Promise<{ lang: string;
queryFn: () => getInvite(code),
});


let user: User | undefined = undefined;
try {
user = await getCurrentUser(false);
} catch (_) {}

return (
<HydrationBoundary state={dehydrate(queryClient)}>
<InviteClient code={code} dict={dict} />
<InviteClient code={code} dict={dict} currentUser={user} />
</HydrationBoundary>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useQuery } from "@tanstack/react-query";
export default function Dashboard() {
const { data } = useQuery({
queryKey: ['user'],
queryFn: getCurrentUser,
queryFn: () => getCurrentUser(),
});

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default function CampaignDetails({ campaignId, orgId, dict }: { campaignI
</Link>
</ButtonGroup>
<ButtonGroup>
<CopyButton value={`${process.env.NEXT_PUBLIC_APP_URL}/campaign/${campaign?.organisation_slug}/${campaign?.campaign_slug}`}>
<CopyButton value={`https://chaos.devsoc.app/campaign/${campaign?.organisation_slug}/${campaign?.campaign_slug}`}>
<Share className="w-4 h-4" /> {dict.dashboard.campaigns.share_link}
</CopyButton>
<CopyButton value={campaignId}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default async function Layout({ children, params }: { children: React.Rea

await queryClient.prefetchQuery({
queryKey: ['user'],
queryFn: getCurrentUser,
queryFn: () => getCurrentUser(),
});

await queryClient.prefetchQuery({
Expand Down

This file was deleted.

10 changes: 9 additions & 1 deletion frontend-nextjs/src/components/admin-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { Building2, ChevronDown, Mail, Megaphone, Plus, Settings, User, Users } from "lucide-react"
import { Building2, ChevronDown, LogOut, Mail, Megaphone, Plus, Settings, User, Users } from "lucide-react"

import {
Sidebar,
Expand Down Expand Up @@ -138,6 +138,14 @@ export function AdminSidebar({ userRole, dict }: AdminSidebarProps) {
</a>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton asChild>
<a href={`${process.env.NEXT_PUBLIC_API_BASE_URL || "https://chaos-api.devsoc.app"}/auth/logout`}>
<LogOut />
<span>{dict.common.logout}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
Expand Down
3 changes: 3 additions & 0 deletions frontend-nextjs/src/dictionaries/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"application_deadline": "Application Deadline",
"apply": "Apply",

"logout": "Logout",

"status": "Status",
"email": "Email",

Expand Down Expand Up @@ -166,6 +168,7 @@
"title": "You're invited",
"invited_by": "has invited you to join their organisation on Chaos",
"sent_to": "This invite was sent to",
"sent_to_note": "Please use the correct Google account to sign in.",
"login_cta": "Click here to create a Chaos account",
"accept_cta": "Accept invite",
"used": "This invite has already been used",
Expand Down
3 changes: 3 additions & 0 deletions frontend-nextjs/src/dictionaries/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"application_deadline": "申请截止日期",
"apply": "申请",

"logout": "登出",

"status": "状态",
"email": "邮箱",

Expand Down Expand Up @@ -157,6 +159,7 @@
"title": "你被邀请了",
"invited_by": "邀请你加入他们的组织 on Chaos",
"sent_to": "此邀请发送给",
"sent_to_note": "请使用正确的 Google 账户登录。",
"login_cta": "点击这里创建 Chaos 账户",
"accept_cta": "接受邀请",
"used": "此邀请已被使用",
Expand Down
4 changes: 2 additions & 2 deletions frontend-nextjs/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export async function apiRequest<T>(
path: string,
options: RequestOptions = {}
): Promise<T> {
const { method = "GET", body, headers = {}, okRequiredOtherwiseLogin = false } = options;
const { method = "GET", body, headers = {}, okRequiredOtherwiseLogin = true } = options;

const requestHeaders: Record<string, string> = { ...headers };

Expand Down Expand Up @@ -71,7 +71,7 @@ export async function apiRequest<T>(
const response = await fetch(url, fetchOptions);

if (!response.ok) {
if (response.status === 401 || okRequiredOtherwiseLogin) {
if (response.status === 401 && okRequiredOtherwiseLogin) {
if (isServer) {
const { redirect } = await import("next/navigation");
const { headers } = await import("next/headers");
Expand Down
Loading
Loading