fix: use new headscale user api routes
This commit is contained in:
parent
e6580eed2c
commit
774fdb7be2
@ -1,43 +0,0 @@
|
||||
import { HomeIcon, PasskeyFillIcon } from '@primer/octicons-react';
|
||||
|
||||
import Card from '~/components/Card';
|
||||
import Link from '~/components/Link';
|
||||
|
||||
import Add from '../dialogs/add';
|
||||
|
||||
interface Props {
|
||||
readonly magic: string | undefined;
|
||||
}
|
||||
|
||||
export default function Auth({ magic }: Props) {
|
||||
return (
|
||||
<Card variant="flat" className="mb-8 w-full max-w-full p-0">
|
||||
<div className="flex flex-col md:flex-row">
|
||||
<div className="w-full p-4 border-b md:border-b-0 border-headplane-100 dark:border-headplane-800">
|
||||
<HomeIcon className="w-5 h-5 mb-2" />
|
||||
<h2 className="font-medium mb-1">Basic Authentication</h2>
|
||||
<p className="text-sm text-headplane-600 dark:text-headplane-300">
|
||||
Users are not managed externally. Using OpenID Connect can create a
|
||||
better experience when using Headscale.{' '}
|
||||
<Link
|
||||
to="https://headscale.net/stable/ref/oidc"
|
||||
name="Headscale OIDC Documentation"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full p-4 md:border-l border-headplane-100 dark:border-headplane-800">
|
||||
<PasskeyFillIcon className="w-5 h-5 mb-2" />
|
||||
<h2 className="font-medium mb-1">User Management</h2>
|
||||
<p className="text-sm text-headplane-600 dark:text-headplane-300">
|
||||
You can add, remove, and rename users here.
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<Add />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
69
app/routes/users/components/manage-banner.tsx
Normal file
69
app/routes/users/components/manage-banner.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { Building2, House, Key } from 'lucide-react';
|
||||
import Card from '~/components/Card';
|
||||
import Link from '~/components/Link';
|
||||
import { HeadplaneConfig } from '~/utils/state';
|
||||
import CreateUser from '../dialogs/create-user';
|
||||
|
||||
interface Props {
|
||||
oidc?: NonNullable<HeadplaneConfig['oidc']>;
|
||||
}
|
||||
|
||||
export default function ManageBanner({ oidc }: Props) {
|
||||
return (
|
||||
<Card variant="flat" className="mb-8 w-full max-w-full p-0">
|
||||
<div className="flex flex-col md:flex-row">
|
||||
<div className="w-full p-4 border-b md:border-b-0 border-headplane-100 dark:border-headplane-800">
|
||||
{oidc ? (
|
||||
<Building2 className="w-5 h-5 mb-2" />
|
||||
) : (
|
||||
<House className="w-5 h-5 mb-2" />
|
||||
)}
|
||||
<h2 className="font-medium mb-1">
|
||||
{oidc ? 'OpenID Connect' : 'User Authentication'}
|
||||
</h2>
|
||||
<p className="text-sm text-headplane-600 dark:text-headplane-300">
|
||||
{oidc ? (
|
||||
<>
|
||||
Users are managed through your{' '}
|
||||
<Link to={oidc.issuer} name="OIDC Provider">
|
||||
OpenID Connect provider
|
||||
</Link>
|
||||
{'. '}
|
||||
Groups and user information do not automatically sync.{' '}
|
||||
<Link
|
||||
to="https://headscale.net/stable/ref/oidc"
|
||||
name="Headscale OIDC Documentation"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Users are not managed externally. Using OpenID Connect can
|
||||
create a better experience when using Headscale.{' '}
|
||||
<Link
|
||||
to="https://headscale.net/stable/ref/oidc"
|
||||
name="Headscale OIDC Documentation"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full p-4 md:border-l border-headplane-100 dark:border-headplane-800">
|
||||
<Key className="w-5 h-5 mb-2" />
|
||||
<h2 className="font-medium mb-1">User Management</h2>
|
||||
<p className="text-sm text-headplane-600 dark:text-headplane-300">
|
||||
{oidc
|
||||
? 'You can still add users manually, however it is recommended that you manage users through your OIDC provider.'
|
||||
: 'You can add, remove, and rename users here.'}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<CreateUser />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
import { OrganizationIcon, PasskeyFillIcon } from '@primer/octicons-react';
|
||||
import Card from '~/components/Card';
|
||||
import Link from '~/components/Link';
|
||||
import { HeadplaneConfig } from '~/utils/state';
|
||||
import Add from '../dialogs/add';
|
||||
|
||||
interface Props {
|
||||
readonly oidc: NonNullable<HeadplaneConfig['oidc']>;
|
||||
}
|
||||
|
||||
export default function Oidc({ oidc }: Props) {
|
||||
return (
|
||||
<Card variant="flat" className="mb-8 w-full max-w-full p-0">
|
||||
<div className="flex flex-col md:flex-row">
|
||||
<div className="w-full p-4 border-b md:border-b-0 border-headplane-100 dark:border-headplane-800">
|
||||
<OrganizationIcon className="w-5 h-5 mb-2" />
|
||||
<h2 className="font-medium mb-1">OpenID Connect</h2>
|
||||
<p className="text-sm text-headplane-600 dark:text-headplane-300">
|
||||
Users are managed through your{' '}
|
||||
<Link to={oidc.issuer} name="OIDC Provider">
|
||||
OpenID Connect provider
|
||||
</Link>
|
||||
{'. '}
|
||||
Groups and user information do not automatically sync.{' '}
|
||||
<Link
|
||||
to="https://headscale.net/stable/ref/oidc"
|
||||
name="Headscale OIDC Documentation"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full p-4 md:border-l border-headplane-100 dark:border-headplane-800">
|
||||
<PasskeyFillIcon className="w-5 h-5 mb-2" />
|
||||
<h2 className="font-medium mb-1">User Management</h2>
|
||||
<p className="text-sm text-headplane-600 dark:text-headplane-300">
|
||||
You can still add users manually, however it is recommended that you
|
||||
manage users through your OIDC provider.
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<Add />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
import Code from '~/components/Code';
|
||||
import Dialog from '~/components/Dialog';
|
||||
import Input from '~/components/Input';
|
||||
|
||||
export default function Add() {
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog.Button>Add a new user</Dialog.Button>
|
||||
<Dialog.Panel>
|
||||
<Dialog.Title>Add a new user</Dialog.Title>
|
||||
<Dialog.Text className="mb-8">
|
||||
Enter a username to create a new user. Usernames can be addressed when
|
||||
managing ACL policies.
|
||||
</Dialog.Text>
|
||||
<input type="hidden" name="_method" value="create" />
|
||||
<Input
|
||||
isRequired
|
||||
name="username"
|
||||
label="Username"
|
||||
placeholder="my-new-user"
|
||||
/>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
33
app/routes/users/dialogs/create-user.tsx
Normal file
33
app/routes/users/dialogs/create-user.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import Dialog from '~/components/Dialog';
|
||||
import Input from '~/components/Input';
|
||||
|
||||
// TODO: Support image upload for user avatars
|
||||
export default function CreateUser() {
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog.Button>Add a new user</Dialog.Button>
|
||||
<Dialog.Panel>
|
||||
<Dialog.Title>Add a new user</Dialog.Title>
|
||||
<Dialog.Text className="mb-6">
|
||||
Enter a username to create a new user. Usernames can be addressed when
|
||||
managing ACL policies.
|
||||
</Dialog.Text>
|
||||
<input type="hidden" name="action_id" value="create_user" />
|
||||
<div className="flex flex-col gap-4">
|
||||
<Input
|
||||
isRequired
|
||||
name="username"
|
||||
label="Username"
|
||||
placeholder="my-new-user"
|
||||
/>
|
||||
<Input
|
||||
name="display_name"
|
||||
label="Display Name"
|
||||
placeholder="John Doe"
|
||||
/>
|
||||
<Input name="email" label="Email" placeholder="name@example.com" />
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
30
app/routes/users/dialogs/delete-user.tsx
Normal file
30
app/routes/users/dialogs/delete-user.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { X } from 'lucide-react';
|
||||
import Dialog from '~/components/Dialog';
|
||||
import { User } from '~/types';
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
}
|
||||
|
||||
// TODO: Warn that OIDC users will be recreated on next login
|
||||
export default function DeleteUser({ user }: Props) {
|
||||
const name =
|
||||
(user.displayName?.length ?? 0) > 0 ? user.displayName : user.name;
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog.IconButton label={`Delete ${name}`}>
|
||||
<X className="p-0.5" />
|
||||
</Dialog.IconButton>
|
||||
<Dialog.Panel>
|
||||
<Dialog.Title>Delete {name}?</Dialog.Title>
|
||||
<Dialog.Text className="mb-6">
|
||||
Are you sure you want to delete {name}? A deleted user cannot be
|
||||
recovered.
|
||||
</Dialog.Text>
|
||||
<input type="hidden" name="action_id" value="delete_user" />
|
||||
<input type="hidden" name="user_id" value={user.id} />
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import { X } from 'lucide-react';
|
||||
import Code from '~/components/Code';
|
||||
import Dialog from '~/components/Dialog';
|
||||
|
||||
interface Props {
|
||||
username: string;
|
||||
}
|
||||
|
||||
export default function Remove({ username }: Props) {
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog.IconButton label={`Delete ${username}`}>
|
||||
<X className="p-0.5" />
|
||||
</Dialog.IconButton>
|
||||
<Dialog.Panel>
|
||||
<Dialog.Title>Delete {username}?</Dialog.Title>
|
||||
<Dialog.Text className="mb-8">
|
||||
Are you sure you want to delete {username}? A deleted user cannot be
|
||||
recovered.
|
||||
</Dialog.Text>
|
||||
<input type="hidden" name="_method" value="delete" />
|
||||
<input type="hidden" name="username" value={username} />
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@ -1,35 +1,34 @@
|
||||
import { Pencil } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import Dialog from '~/components/Dialog';
|
||||
import Input from '~/components/Input';
|
||||
import { User } from '~/types';
|
||||
|
||||
interface Props {
|
||||
username: string;
|
||||
user: User;
|
||||
}
|
||||
|
||||
// TODO: Server side validation before submitting
|
||||
export default function Rename({ username }: Props) {
|
||||
const [newName, setNewName] = useState(username);
|
||||
|
||||
export default function RenameUser({ user }: Props) {
|
||||
return (
|
||||
<Dialog>
|
||||
<Dialog.IconButton label={`Rename ${username}`}>
|
||||
<Dialog.IconButton label={`Rename ${user.name}`}>
|
||||
<Pencil className="p-1" />
|
||||
</Dialog.IconButton>
|
||||
<Dialog.Panel>
|
||||
<Dialog.Title>Rename {username}?</Dialog.Title>
|
||||
<Dialog.Text className="mb-8">
|
||||
Enter a new username for {username}. Changing a username will not
|
||||
<Dialog.Title>Rename {user.name}?</Dialog.Title>
|
||||
<Dialog.Text className="mb-6">
|
||||
Enter a new username for {user.name}. Changing a username will not
|
||||
update any ACL policies that may refer to this user by their old
|
||||
username.
|
||||
</Dialog.Text>
|
||||
<input type="hidden" name="_method" value="rename" />
|
||||
<input type="hidden" name="old" value={username} />
|
||||
<input type="hidden" name="action_id" value="rename_user" />
|
||||
<input type="hidden" name="user_id" value={user.id} />
|
||||
<Input
|
||||
isRequired
|
||||
name="new"
|
||||
name="new_name"
|
||||
label="Username"
|
||||
placeholder="my-new-name"
|
||||
defaultValue={user.name}
|
||||
/>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
@ -2,7 +2,7 @@ import { DataRef, DndContext, useDraggable, useDroppable } from '@dnd-kit/core';
|
||||
import { PersonIcon } from '@primer/octicons-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
|
||||
import { useActionData, useLoaderData, useSubmit } from 'react-router';
|
||||
import { useLoaderData, useSubmit } from 'react-router';
|
||||
import { ClientOnly } from 'remix-utils/client-only';
|
||||
|
||||
import Attribute from '~/components/Attribute';
|
||||
@ -11,16 +11,14 @@ import { ErrorPopup } from '~/components/Error';
|
||||
import StatusCircle from '~/components/StatusCircle';
|
||||
import type { Machine, User } from '~/types';
|
||||
import cn from '~/utils/cn';
|
||||
import { del, post, pull } from '~/utils/headscale';
|
||||
import { send } from '~/utils/res';
|
||||
import { pull } from '~/utils/headscale';
|
||||
import { getSession } from '~/utils/sessions.server';
|
||||
|
||||
import { hp_getConfig, hs_getConfig } from '~/utils/state';
|
||||
import toast from '~/utils/toast';
|
||||
import Auth from './components/auth';
|
||||
import Oidc from './components/oidc';
|
||||
import Remove from './dialogs/remove';
|
||||
import Rename from './dialogs/rename';
|
||||
import ManageBanner from './components/manage-banner';
|
||||
import DeleteUser from './dialogs/delete-user';
|
||||
import RenameUser from './dialogs/rename-user';
|
||||
import { userAction } from './user-actions';
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
@ -52,94 +50,18 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const session = await getSession(request.headers.get('Cookie'));
|
||||
if (!session.has('hsApiKey')) {
|
||||
return send({ message: 'Unauthorized' }, 401);
|
||||
}
|
||||
|
||||
const data = await request.formData();
|
||||
if (!data.has('_method')) {
|
||||
return send({ message: 'No method provided' }, 400);
|
||||
}
|
||||
|
||||
const method = String(data.get('_method'));
|
||||
|
||||
switch (method) {
|
||||
case 'create': {
|
||||
if (!data.has('username')) {
|
||||
return send({ message: 'No name provided' }, 400);
|
||||
}
|
||||
|
||||
const username = String(data.get('username'));
|
||||
await post('v1/user', session.get('hsApiKey')!, {
|
||||
name: username,
|
||||
});
|
||||
|
||||
return { message: `User ${username} created` };
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
if (!data.has('username')) {
|
||||
return send({ message: 'No name provided' }, 400);
|
||||
}
|
||||
|
||||
const username = String(data.get('username'));
|
||||
await del(`v1/user/${username}`, session.get('hsApiKey')!);
|
||||
return { message: `User ${username} deleted` };
|
||||
}
|
||||
|
||||
case 'rename': {
|
||||
if (!data.has('old') || !data.has('new')) {
|
||||
return send({ message: 'No old or new name provided' }, 400);
|
||||
}
|
||||
|
||||
const old = String(data.get('old'));
|
||||
const newName = String(data.get('new'));
|
||||
await post(`v1/user/${old}/rename/${newName}`, session.get('hsApiKey')!);
|
||||
return { message: `User ${old} renamed to ${newName}` };
|
||||
}
|
||||
|
||||
case 'move': {
|
||||
if (!data.has('id') || !data.has('to') || !data.has('name')) {
|
||||
return send({ message: 'No ID or destination provided' }, 400);
|
||||
}
|
||||
|
||||
const id = String(data.get('id'));
|
||||
const to = String(data.get('to'));
|
||||
const name = String(data.get('name'));
|
||||
|
||||
try {
|
||||
await post(`v1/node/${id}/user?user=${to}`, session.get('hsApiKey')!);
|
||||
return { message: `Moved ${name} to ${to}` };
|
||||
} catch {
|
||||
return send({ message: `Failed to move ${name} to ${to}` }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
default: {
|
||||
return send({ message: 'Invalid method' }, 400);
|
||||
}
|
||||
}
|
||||
export async function action(data: ActionFunctionArgs) {
|
||||
return userAction(data);
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const [users, setUsers] = useState(data.users);
|
||||
const actionData = useActionData<typeof action>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!actionData) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast(actionData.message);
|
||||
if (actionData.message.startsWith('Failed')) {
|
||||
setUsers(data.users);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [actionData]);
|
||||
const [users, setUsers] = useState<UserMachine[]>(data.users);
|
||||
|
||||
// This useEffect is entirely for the purpose of updating the users when the
|
||||
// drag and drop changes the machines between users. It's pretty hacky, but
|
||||
// the idea is to treat data.users as the source of truth and update the
|
||||
// local state when it changes.
|
||||
useEffect(() => {
|
||||
setUsers(data.users);
|
||||
}, [data.users]);
|
||||
@ -151,7 +73,7 @@ export default function Page() {
|
||||
Manage the users in your network and their permissions. Tip: You can
|
||||
drag machines between users to change ownership.
|
||||
</p>
|
||||
{data.oidc ? <Oidc oidc={data.oidc} /> : <Auth magic={data.magic} />}
|
||||
<ManageBanner oidc={data.oidc} />
|
||||
<ClientOnly fallback={<Users users={users} />}>
|
||||
{() => (
|
||||
<InteractiveUsers
|
||||
@ -218,10 +140,9 @@ function InteractiveUsers({ users, setUsers, magic }: UserProps) {
|
||||
|
||||
setUsers?.(newUsers);
|
||||
const data = new FormData();
|
||||
data.append('_method', 'move');
|
||||
data.append('id', active.id.toString());
|
||||
data.append('to', over.id.toString());
|
||||
data.append('name', reference.current.givenName);
|
||||
data.append('action_id', 'change_owner');
|
||||
data.append('user_id', over.id.toString());
|
||||
data.append('node_id', reference.current.id);
|
||||
|
||||
submit(data, {
|
||||
method: 'POST',
|
||||
@ -293,9 +214,9 @@ function UserCard({ user, magic }: CardProps) {
|
||||
<span className="text-lg font-mono">{user.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Rename username={user.name} />
|
||||
<RenameUser user={user} />
|
||||
{user.machines.length === 0 ? (
|
||||
<Remove username={user.name} />
|
||||
<DeleteUser user={user} />
|
||||
) : undefined}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
81
app/routes/users/user-actions.ts
Normal file
81
app/routes/users/user-actions.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { ActionFunctionArgs, data } from 'react-router';
|
||||
import { del, post } from '~/utils/headscale';
|
||||
import { auth } from '~/utils/sessions.server';
|
||||
|
||||
export async function userAction({ request }: ActionFunctionArgs) {
|
||||
const session = await auth(request);
|
||||
if (!session) {
|
||||
return data({ success: false }, 401);
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const action = formData.get('action_id')?.toString();
|
||||
if (!action) {
|
||||
return data({ success: false }, 400);
|
||||
}
|
||||
|
||||
const apiKey = session.get('hsApiKey');
|
||||
if (!apiKey) {
|
||||
return data({ success: false }, 401);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'create_user':
|
||||
return createUser(formData, apiKey);
|
||||
case 'delete_user':
|
||||
return deleteUser(formData, apiKey);
|
||||
case 'rename_user':
|
||||
return renameUser(formData, apiKey);
|
||||
case 'change_owner':
|
||||
return changeOwner(formData, apiKey);
|
||||
default:
|
||||
return data({ success: false }, 400);
|
||||
}
|
||||
}
|
||||
|
||||
async function createUser(formData: FormData, apiKey: string) {
|
||||
const name = formData.get('username')?.toString();
|
||||
const displayName = formData.get('display_name')?.toString();
|
||||
const email = formData.get('email')?.toString();
|
||||
|
||||
if (!name) {
|
||||
return data({ success: false }, 400);
|
||||
}
|
||||
|
||||
await post('v1/user', apiKey, {
|
||||
name,
|
||||
displayName,
|
||||
email,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteUser(formData: FormData, apiKey: string) {
|
||||
const userId = formData.get('user_id')?.toString();
|
||||
if (!userId) {
|
||||
return data({ success: false }, 400);
|
||||
}
|
||||
|
||||
await del(`v1/user/${userId}`, apiKey);
|
||||
}
|
||||
|
||||
async function renameUser(formData: FormData, apiKey: string) {
|
||||
const userId = formData.get('user_id')?.toString();
|
||||
const newName = formData.get('new_name')?.toString();
|
||||
if (!userId || !newName) {
|
||||
return data({ success: false }, 400);
|
||||
}
|
||||
|
||||
await post(`v1/user/${userId}/rename/${newName}`, apiKey);
|
||||
}
|
||||
|
||||
async function changeOwner(formData: FormData, apiKey: string) {
|
||||
const userId = formData.get('user_id')?.toString();
|
||||
const nodeId = formData.get('node_id')?.toString();
|
||||
if (!userId || !nodeId) {
|
||||
return data({ success: false }, 400);
|
||||
}
|
||||
|
||||
await post(`v1/node/${nodeId}/user`, apiKey, {
|
||||
user: userId,
|
||||
});
|
||||
}
|
||||
@ -2,4 +2,9 @@ export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
displayName?: string;
|
||||
email?: string;
|
||||
providerId?: string;
|
||||
provider?: string;
|
||||
profilePicUrl?: string;
|
||||
}
|
||||
|
||||
@ -53,6 +53,7 @@ export function getSession(cookie: string | null) {
|
||||
return sessionStorage.getSession(cookie);
|
||||
}
|
||||
|
||||
export type ServerSession = Session<SessionData, SessionFlashData>;
|
||||
export async function auth(request: Request) {
|
||||
if (!sessionStorage) {
|
||||
return false;
|
||||
@ -64,7 +65,7 @@ export async function auth(request: Request) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return session;
|
||||
}
|
||||
|
||||
export function destroySession(session: Session) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user