feat: add write adn read scopes to admin

This commit is contained in:
SevicheCC 2023-06-05 16:08:57 +08:00
parent 529fe6d4cf
commit b541573d77
Signed by: SevicheCC
GPG key ID: C577000000000000
6 changed files with 195 additions and 126 deletions

View file

@ -4,6 +4,9 @@ import ClientOnly from "@/components/ClientOnly";
export default function Home() {
return (
<main>
<h1 className="mb-5 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl">
M-OAuth
</h1>
<ClientOnly>
<InputForm />
</ClientOnly>

View file

@ -1,10 +1,10 @@
'use client'
"use client";
import * as z from "zod"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button"
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
@ -13,36 +13,84 @@ import {
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { readScopes, writeScopes, adminScopes } from "@/lib/utils"
import ScopeSection from "./ScopeSection"
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
READ_SCOPES,
WRITE_SCOPES,
ADMIN_READ_SCOPES,
ADMIN_WRITE_SCOPES,
} from "@/lib/utils";
import ScopeSection from "./ScopeSection";
export type MethodType =
| "read"
| "write"
| "follow"
| "crypto"
| "follow"
| "admin"
| "push";
export interface ScopeInfo {
method: MethodType;
scopes?: string[] | string[][];
description: string;
}
const scopesInfo: ScopeInfo[] = [
{
method: "read",
scopes: READ_SCOPES,
description: "read account's data",
},
{
method: "write",
scopes: WRITE_SCOPES,
description: "modify account's data",
},
{
method: "follow",
description: "modify account relationships,deprecated in 3.5.0 and newer.",
},
{
method: "push",
description: "receive push notifications",
},
{
method: "admin",
scopes: [ADMIN_READ_SCOPES, ADMIN_WRITE_SCOPES],
description: "read all data on the server",
},
{
method: "crypto",
description: "use end-to-end encryption",
},
];
const formSchema = z.object({
instance: z.string().trim(),
clientName: z.string().trim(),
redirectUris: z.string().url().trim(),
scopes: z.string().array().nonempty().optional(),
website: z.string().trim().optional()
})
website: z.string().trim().optional(),
});
const InputForm = () => {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
instance:'https://',
clientName: '',
redirectUris: 'urn:ietf:wg:oauth:2.0:oob',
instance: "https://",
clientName: "",
redirectUris: "",
},
})
});
function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values)
console.log(values);
}
return (
<Form {...form}>
<h1 className="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl mb-5">M-OAuth</h1>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
@ -51,7 +99,7 @@ const InputForm = () => {
<FormItem>
<FormLabel>Instance</FormLabel>
<FormControl>
<Input placeholder="mastodon.social" {...field} />
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -92,7 +140,13 @@ const InputForm = () => {
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>Use <code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs font-semibold">urn:ietf:wg:oauth:2.0:oob</code> for local tests</FormDescription>
<FormDescription>
Use{" "}
<code className="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs font-semibold">
urn:ietf:wg:oauth:2.0:oob
</code>{" "}
for local tests
</FormDescription>
<FormMessage />
</FormItem>
)}
@ -100,18 +154,27 @@ const InputForm = () => {
<FormField
control={form.control}
name="scopes"
render={({ field }) => (
render={() => (
<FormItem>
<FormLabel>Scopes</FormLabel>
<FormControl className="flex flex-col gap-2">
<FormField
control={form.control}
name="scopes"
render={({ field }) => (
<FormItem>
<div className="flex flex-col gap-2">
<ScopeSection method="read" scopes={readScopes} />
<ScopeSection method="write" scopes={writeScopes} />
<ScopeSection method="admin" scopes={adminScopes} />
<ScopeSection method="follow" />
<ScopeSection method="push" />
<ScopeSection method="crypto" />
{scopesInfo.map((info) => (
<ScopeSection
key={info.method}
info={info}
field={field}
/>
))}
</div>
</FormItem>
)}
></FormField>
</FormControl>
<FormMessage />
</FormItem>
@ -120,7 +183,6 @@ const InputForm = () => {
<Button type="submit">Submit</Button>
</form>
</Form>
);
};
export default InputForm;

View file

@ -0,0 +1,28 @@
import { Checkbox } from "@radix-ui/react-checkbox";
import { MethodType } from "./InputForm";
interface ScopeCheckboxProps {
scope: string;
method: MethodType;
}
const ScopeCheckbox: React.FC<ScopeCheckboxProps> = ({ scope, method }) => {
return (
<div className={`items-top flex space-x-2 hover:cursor-pointer`}>
<Checkbox id={`${scope}`} />
<div className="grid gap-1.5 leading-none">
<label
htmlFor={`${scope}`}
className="text-sm font-medium leading-none hover:cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{method == "admin" && (
<span className="text-slate-500">{scope.split(":")[1]} : </span>
)}
{scope.split(":").slice(-1)}
</label>
</div>
</div>
);
};
export default ScopeCheckbox;

View file

@ -9,22 +9,21 @@ import {
import { Button } from "@/components/ui/button";
import { ChevronsUpDown } from "lucide-react";
import { ReadScope, AdminScope, WriteScope } from "@/lib/types";
import { MethodType } from "@/lib/types";
import { ControllerRenderProps } from "react-hook-form";
import { ScopeInfo } from "./InputForm";
import ScopeCheckbox from "./ScopeCheckbox";
interface ScopeSectionProps {
method: MethodType;
scopes?: ReadScope[] | WriteScope[] | AdminScope[];
info: ScopeInfo;
field: any;
}
const ScopeSection: React.FC<ScopeSectionProps> = ({ method, scopes}) => {
const ScopeSection: React.FC<ScopeSectionProps> = ({ info, field }) => {
const { method, description, scopes } = info;
return (
<Collapsible className="flex flex-col rounded-md bg-slate-50 px-4 py-3">
<div className="flex justify-between">
<div className="items-top flex space-x-2 ">
<Checkbox id={method}/>
<Checkbox id={method} />
<div className="grid gap-1.5 leading-none">
<label
htmlFor={method}
@ -32,14 +31,7 @@ const ScopeSection: React.FC<ScopeSectionProps> = ({ method, scopes}) => {
>
{method}
</label>
<p className="text-xs text-muted-foreground">
{method === "read" && "read your account's data"}
{method === "write" && "modify your account's data"}
{method === "admin" && "read data on the server"}
{method === "follow" && "modify account relationships"}
{method === "push" && "receive your push notifications"}
{method === "crypto" && "use end-to-end encryption"}
</p>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
</div>
{scopes && (
@ -51,28 +43,30 @@ const ScopeSection: React.FC<ScopeSectionProps> = ({ method, scopes}) => {
</CollapsibleTrigger>
)}
</div>
<CollapsibleContent>
{scopes && (
<div className="grid grid-cols-1 gap-y-4 pb-2 ps-6 pt-5 md:grid-cols-2 ">
{scopes.map((scope) => (
<CollapsibleContent>
<div
className="items-top flex space-x-2 hover:cursor-pointer"
key={`${method}:${scope}`}
className={`grid grid-cols-1 pb-2 ps-6 pt-5 md:grid-cols-2 ${
method === "admin" ? "" : "gap-2"
}`}
>
<Checkbox id={`${method + scope}`} />
<div className="grid gap-1.5 leading-none">
<label
htmlFor={`${method + scope}`}
className="text-sm font-medium leading-none hover:cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
{method === "admin"
? (scopes as string[][]).map((items) => (
<div
key={`${items}${method}`}
className="flex flex-col gap-2"
>
{scope}
</label>
</div>
</div>
{items.map((item) => (
<ScopeCheckbox scope={item} key={item} method={method} />
))}
</div>
))
: (scopes as string[]).map((scope) => (
<ScopeCheckbox scope={scope} key={scope} method={method} />
))}
</div>
)}
</CollapsibleContent>
)}
</Collapsible>
);
};

View file

@ -1,28 +0,0 @@
export type MethodType = "read" | "write" | "follow" | "push" | 'crypto' | 'admin'
export type ReadScope =
|"account"
| "blocks"
| "bookmarks"
| "favourites"
| "filters"
| "filters"
| "lists"
| "mutes"
| "notifications"
| "search"
| "statuses";
export type WriteScope = Omit<ReadScope, 'search'>
| 'conversations'
| 'media'
| "reports"
export type AdminScope =
"account"
| "reports"
| "domain_allows"
| "domain_blocks"
| "ip_blocks"
| "email_domain_blocks"
| "canonical_email_blocks";

View file

@ -1,45 +1,55 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { ReadScope, WriteScope, AdminScope } from "@/lib/types";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export const readScopes: ReadScope[] = [
"account",
"blocks",
"bookmarks",
"favourites",
"filters",
"lists",
"mutes",
"notifications",
"search",
"statuses",
export const READ_SCOPES = [
"read:accounts",
"read:blocks",
"read:bookmarks",
"read:favourites",
"read:filters",
"read:follows",
"read:lists",
"read:mutes",
"read:notifications",
"read:search",
"read:statuses",
];
export const writeScopes: WriteScope[] = [
"account",
"blocks",
"bookmarks",
"favourites",
"filters",
"lists",
"mutes",
"notifications",
"statuses",
"conversations",
"media",
"reports",
export const WRITE_SCOPES = [
"write:account",
"write:blocks",
"write:bookmarks",
"write:favourites",
"write:filters",
"write:lists",
"write:mutes",
"write:notifications",
"write:statuses",
"write:conversations",
"write:media",
"write:reports",
];
export const adminScopes: AdminScope[] = [
"account",
"reports",
"domain_allows",
"domain_blocks",
"ip_blocks",
"email_domain_blocks",
"canonical_email_blocks",
export const ADMIN_READ_SCOPES = [
"admin:read:account",
"admin:read:reports",
"admin:read:domain_allows",
"admin:read:domain_blocks",
"admin:read:ip_blocks",
"admin:read:email_domain_blocks",
"admin:read:canonical_email_blocks",
];
export const ADMIN_WRITE_SCOPES = [
"admin:write:account",
"admin:write:reports",
"admin:write:domain_allows",
"admin:write:domain_blocks",
"admin:write:ip_blocks",
"admin:write:email_domain_blocks",
"admin:write:canonical_email_blocks",
];