mirror of
https://github.com/fosrl/pangolin.git
synced 2025-07-12 23:15:00 +02:00
allow wildcard emails in email whitelist
This commit is contained in:
parent
9f1f2910e4
commit
61b34c8b16
4 changed files with 104 additions and 16 deletions
|
@ -13,9 +13,7 @@ import { NextFunction, Request, Response } from "express";
|
|||
import createHttpError from "http-errors";
|
||||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import {
|
||||
createResourceSession,
|
||||
} from "@server/auth/sessions/resource";
|
||||
import { createResourceSession } from "@server/auth/sessions/resource";
|
||||
import { isValidOtp, sendResourceOtpEmail } from "@server/auth/resourceOtp";
|
||||
import logger from "@server/logger";
|
||||
|
||||
|
@ -90,10 +88,37 @@ export async function authWithWhitelist(
|
|||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.limit(1);
|
||||
|
||||
const resource = result?.resources;
|
||||
const org = result?.orgs;
|
||||
const whitelistedEmail = result?.resourceWhitelist;
|
||||
let resource = result?.resources;
|
||||
let org = result?.orgs;
|
||||
let whitelistedEmail = result?.resourceWhitelist;
|
||||
|
||||
if (!whitelistedEmail) {
|
||||
// if email is not found, check for wildcard email
|
||||
const wildcard = "*@" + email.split("@")[1];
|
||||
|
||||
logger.debug("Checking for wildcard email: " + wildcard)
|
||||
|
||||
const [result] = await db
|
||||
.select()
|
||||
.from(resourceWhitelist)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceWhitelist.resourceId, resourceId),
|
||||
eq(resourceWhitelist.email, wildcard)
|
||||
)
|
||||
)
|
||||
.leftJoin(
|
||||
resources,
|
||||
eq(resources.resourceId, resourceWhitelist.resourceId)
|
||||
)
|
||||
.leftJoin(orgs, eq(orgs.orgId, resources.orgId))
|
||||
.limit(1);
|
||||
|
||||
resource = result?.resources;
|
||||
org = result?.orgs;
|
||||
whitelistedEmail = result?.resourceWhitelist;
|
||||
|
||||
// if wildcard is still not found, return unauthorized
|
||||
if (!whitelistedEmail) {
|
||||
return next(
|
||||
createHttpError(
|
||||
|
@ -105,6 +130,7 @@ export async function authWithWhitelist(
|
|||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!org) {
|
||||
return next(
|
||||
|
|
|
@ -12,7 +12,17 @@ import { and, eq } from "drizzle-orm";
|
|||
const setResourceWhitelistBodySchema = z
|
||||
.object({
|
||||
emails: z
|
||||
.array(z.string().email())
|
||||
.array(
|
||||
z
|
||||
.string()
|
||||
.email()
|
||||
.or(
|
||||
z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, {
|
||||
message:
|
||||
"Invalid email address. Wildcard (*) must be the entire local part."
|
||||
})
|
||||
)
|
||||
)
|
||||
.max(50)
|
||||
.transform((v) => v.map((e) => e.toLowerCase()))
|
||||
})
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
SettingsSectionFooter
|
||||
} from "@app/components/Settings";
|
||||
import { SwitchInput } from "@app/components/SwitchInput";
|
||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||
|
||||
const UsersRolesFormSchema = z.object({
|
||||
roles: z.array(
|
||||
|
@ -665,10 +666,12 @@ export default function ResourceAuthenticationPage() {
|
|||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Whitelisted Emails
|
||||
<InfoPopup
|
||||
text="Whitelisted Emails"
|
||||
info="Only users with these email addresses will be able to access this resource. They will be prompted to enter a one-time password sent to their email. Wildcards (*@example.com) can be used to allow any email address from a domain."
|
||||
/>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
{/* @ts-ignore */}
|
||||
{/* @ts-ignore */}
|
||||
<TagInput
|
||||
{...field}
|
||||
|
@ -681,6 +684,17 @@ export default function ResourceAuthenticationPage() {
|
|||
return z
|
||||
.string()
|
||||
.email()
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.regex(
|
||||
/^\*@[\w.-]+\.[a-zA-Z]{2,}$/,
|
||||
{
|
||||
message:
|
||||
"Invalid email address. Wildcard (*) must be the entire local part."
|
||||
}
|
||||
)
|
||||
)
|
||||
.safeParse(
|
||||
tag
|
||||
).success;
|
||||
|
|
38
src/components/ui/info-popup.tsx
Normal file
38
src/components/ui/info-popup.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Info } from "lucide-react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger
|
||||
} from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface InfoPopupProps {
|
||||
text: string;
|
||||
info: string;
|
||||
}
|
||||
|
||||
export function InfoPopup({ text, info }: InfoPopupProps) {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>{text}</span>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 rounded-full p-0"
|
||||
>
|
||||
<Info className="h-4 w-4" />
|
||||
<span className="sr-only">Show info</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80">
|
||||
<p className="text-sm text-muted-foreground">{info}</p>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue