Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ms2/signin merge users #360

Merged
merged 66 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
5ed014b
style(ms2/signin): shift signin modal up
FelipeTrost Jun 21, 2024
2ef53a9
style(ms2/signin): sign in as guest
FelipeTrost Jun 21, 2024
5b479b1
style(ms2/signin): changed sorting of providers
FelipeTrost Jun 24, 2024
8e3fb01
style(ms2/signin): new layout
FelipeTrost Jun 24, 2024
9b9ed49
style(ms2/signin): continue in as guest
FelipeTrost Jun 24, 2024
ce3da11
style(ms2/signin)
FelipeTrost Jun 29, 2024
7f845f0
style(ms2): changed info color to gray
FelipeTrost Jun 30, 2024
3aedc62
feat(ms2): show guests a warning
FelipeTrost Jun 30, 2024
4f748a0
typos
FelipeTrost Jun 30, 2024
6fc71a5
feat(ms2): show new guests modal for creating process
FelipeTrost Jun 30, 2024
dbedff1
merge main
FelipeTrost Jun 30, 2024
3386e0f
lint
FelipeTrost Jul 2, 2024
0ed9a20
fix: missing dependency
FelipeTrost Jul 2, 2024
0d0d17f
Merge remote-tracking branch 'origin/main' into ms2/signin-style
FelipeTrost Jul 2, 2024
4e3eeb8
Merge branch 'main' into ms2/signin-style
OhKai Jul 7, 2024
8bf39bc
feat(ms2): update email if user is signed in and is verifying email
FelipeTrost Jul 8, 2024
057907e
feat(ms2/profile): modal to change email
FelipeTrost Jul 8, 2024
6e358e9
Merge remote-tracking branch 'origin/main' into ms2/change-email
FelipeTrost Jul 8, 2024
506d584
feat(ms2): verificationToken store
FelipeTrost Jul 10, 2024
c4fb15c
fix(ms2/signin): remove dangerous sign in code
FelipeTrost Jul 10, 2024
edc70cb
feat(ms2): verificationToken server actions
FelipeTrost Jul 10, 2024
cb923c7
feat(ms2/profile): request email change
FelipeTrost Jul 10, 2024
7c567ee
feat(ms2/change-email): page for confirming email change
FelipeTrost Jul 10, 2024
c0f19a1
refactor(ms2): moved signin-email template to lib/email
FelipeTrost Jul 11, 2024
17ba3a0
refactor(ms2/signin-link-email): changed parameter format
FelipeTrost Jul 11, 2024
a60fc8e
feat(ms2/profile): close modal after email change request
FelipeTrost Jul 11, 2024
64e61cf
feat(ms2/change-email): send change email link
FelipeTrost Jul 11, 2024
3d35269
style(ms2/change-email): better feedback
FelipeTrost Jul 11, 2024
d50a5eb
feat(ms2/change-email): cancel email change
FelipeTrost Jul 11, 2024
fde2b45
Merge branch 'main' into ms2/signin-style
OhKai Jul 11, 2024
bb57561
fix(ms2/e2e-tests): sign in as guest
FelipeTrost Jul 18, 2024
7bc7416
Merge branch 'ms2/signin-style' of github.com:PROCEED-Labs/proceed in…
FelipeTrost Jul 18, 2024
bea3c5f
Merge remote-tracking branch 'origin/main' into ms2/signin-style
FelipeTrost Jul 18, 2024
d78f13a
fix(ms2/signin): use callbackUrl if there is one
FelipeTrost Jul 19, 2024
713b8ac
fix(ms2/e2e-tests): use new sign in
FelipeTrost Jul 19, 2024
299724b
Merge branch 'ms2/change-email' into ms2/signin-merge-users
FelipeTrost Jul 19, 2024
61f3148
fix(ms2/signin): check if verification request
FelipeTrost Jul 19, 2024
4191315
Merge branch 'ms2/signin-style' into ms2/signin-merge-users
FelipeTrost Jul 19, 2024
2b75ada
fix(ms2/environments): remove folders when removing environment
FelipeTrost Jul 21, 2024
84620aa
feat(ms2/users): update guest users
FelipeTrost Jul 21, 2024
b25c072
feat(ms2/folders): get all folders
FelipeTrost Jul 21, 2024
6671e75
feat(ms2/folder): move folders to other environments
FelipeTrost Jul 21, 2024
111fb82
feat(ms2/processes): get processes without ability
FelipeTrost Jul 22, 2024
cc4ae73
typo
FelipeTrost Jul 22, 2024
cefc7ec
feat(ms2): transfer guest processes to account
FelipeTrost Jul 22, 2024
65fcc61
fix(ms2/signin): allow guests to sign in to dev users
FelipeTrost Jul 22, 2024
29be1c7
Merge remote-tracking branch 'origin/main' into ms2/signin-merge-users
FelipeTrost Aug 12, 2024
c9bb810
Merge remote-tracking branch 'origin/main' into ms2/signin-merge-users
FelipeTrost Nov 22, 2024
17f7fb3
Merge remote-tracking branch 'origin/main' into ms2/signin-merge-users
FelipeTrost Nov 22, 2024
971be92
refactor(ms2): get processes takes in an spaceId and an ability
FelipeTrost Nov 26, 2024
0969b79
fix: merge conflict
FelipeTrost Nov 26, 2024
3da52a9
feat(ms2/DTOs): updateProcess, moveFolder, updateFolderMetaData
FelipeTrost Nov 26, 2024
30737b5
refactor(ms2/transfer-processes): now works with JWT for passing the …
FelipeTrost Nov 26, 2024
2591ae4
Merge remote-tracking branch 'origin/main' into ms2/signin-merge-users
FelipeTrost Nov 26, 2024
0c177f2
ms2: type fix
FelipeTrost Nov 27, 2024
2161193
fix(ms2/user): type fix + only allow users to update their data if th…
FelipeTrost Nov 27, 2024
8cb424f
Merge branch 'main' into ms2/signin-merge-users
FelipeTrost Nov 28, 2024
bb9166a
Merge branch 'main' into ms2/signin-merge-users
FelipeTrost Nov 29, 2024
28036d8
Merge remote-tracking branch 'origin/main' into ms2/signin-merge-users
FelipeTrost Dec 4, 2024
3a8fcc5
Merge branch 'ms2/signin-merge-users' of github.com:PROCEED-Labs/proc…
FelipeTrost Dec 4, 2024
407c601
typo
FelipeTrost Dec 9, 2024
8f65c0b
fix: added key
FelipeTrost Dec 9, 2024
0d36d9f
fix: typo in filename
FelipeTrost Dec 9, 2024
2c8d66e
fix(ms2/process-transfer-page): combined buttons + changed invalid to…
FelipeTrost Dec 9, 2024
8ec759c
Merge remote-tracking branch 'origin/main' into ms2/signin-merge-users
FelipeTrost Dec 9, 2024
86e4c58
fix: import from DTOs
FelipeTrost Dec 9, 2024
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
12 changes: 11 additions & 1 deletion src/management-system-v2/app/(auth)/signin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { getProviders } from '@/app/api/auth/[...nextauth]/auth-options';
import { getCurrentUser } from '@/components/auth';
import { redirect } from 'next/navigation';
import SignIn from './signin';
import { generateGuestReferenceToken } from '@/lib/reference-guest-user-token';

const dayInMS = 1000 * 60 * 60 * 24;

// take in search query
const SignInPage = async ({ searchParams }: { searchParams: { callbackUrl: string } }) => {
Expand All @@ -13,6 +16,11 @@ const SignInPage = async ({ searchParams }: { searchParams: { callbackUrl: strin
redirect(callbackUrl);
}

// NOTE: expiration should be the same as the expiration for sign in mails
const guestReferenceToken = isGuest
? generateGuestReferenceToken({ guestId: session.user.id }, new Date(Date.now() + dayInMS))
: undefined;

let providers = getProviders();

providers = providers.filter((provider) => !isGuest || 'development-users' !== provider.id);
Expand All @@ -37,7 +45,9 @@ const SignInPage = async ({ searchParams }: { searchParams: { callbackUrl: strin
if (!session) userType = 'none' as const;
else userType = isGuest ? ('guest' as const) : ('user' as const);

return <SignIn providers={providers} userType={userType} />;
return (
<SignIn providers={providers} userType={userType} guestReferenceToken={guestReferenceToken} />
);
};

export default SignInPage;
16 changes: 12 additions & 4 deletions src/management-system-v2/app/(auth)/signin/signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ const signInTitle = (
const SignIn: FC<{
providers: ExtractedProvider[];
userType: 'guest' | 'user' | 'none';
}> = ({ providers, userType }) => {
guestReferenceToken?: string;
}> = ({ providers, userType, guestReferenceToken }) => {
const searchParams = useSearchParams();
const callbackUrl = searchParams.get('callbackUrl') ?? undefined;
const callbackUrlWithGuestRef = guestReferenceToken
? `/transfer-processes?referenceToken=${guestReferenceToken}&callbackUrl=${callbackUrl}`
: callbackUrl;
const authError = searchParams.get('error');

const oauthProviders = providers.filter((provider) => provider.type === 'oauth');
Expand Down Expand Up @@ -150,7 +154,9 @@ const SignIn: FC<{
if (provider.type === 'credentials') {
return (
<Form
onFinish={(values) => signIn(provider.id, { ...values, callbackUrl })}
onFinish={(values) =>
signIn(provider.id, { ...values, callbackUrl: callbackUrlWithGuestRef })
}
key={provider.id}
layout="vertical"
>
Expand All @@ -168,7 +174,9 @@ const SignIn: FC<{
return (
<>
<Form
onFinish={(values) => signIn(provider.id, { ...values, callbackUrl })}
onFinish={(values) =>
signIn(provider.id, { ...values, callbackUrl: callbackUrlWithGuestRef })
}
key={provider.id}
layout="vertical"
>
Expand Down Expand Up @@ -211,7 +219,7 @@ const SignIn: FC<{
style={{ width: '1.5rem', height: 'auto' }}
/>
}
onClick={() => signIn(provider.id, { callbackUrl })}
onClick={() => signIn(provider.id, { callbackUrl: callbackUrlWithGuestRef })}
/>
</Tooltip>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getCurrentEnvironment, getCurrentUser } from '@/components/auth';
import { getCurrentEnvironment } from '@/components/auth';
import Wrapper from './wrapper';
import styles from './page.module.scss';
import Modeler from './modeler';
Expand All @@ -18,11 +18,10 @@ const Process = async ({ params: { processId, environmentId }, searchParams }: P
//console.log('processId', processId);
//console.log('query', searchParams);
const selectedVersionId = searchParams.version ? searchParams.version : undefined;
const { ability } = await getCurrentEnvironment(environmentId);
const { userId } = await getCurrentUser();
const { ability, activeEnvironment } = await getCurrentEnvironment(environmentId);
// Only load bpmn if no version selected.
const process = await getProcess(processId, !selectedVersionId);
const processes = await getProcesses(userId, ability, false);
const processes = await getProcesses(activeEnvironment.spaceId, ability, false);

if (!ability.can('view', toCaslResource('Process', process))) {
throw new Error('Forbidden.');
Expand All @@ -35,7 +34,7 @@ const Process = async ({ params: { processId, environmentId }, searchParams }: P
? process.versions.find((version) => version.id === selectedVersionId)
: undefined;

// Since the user is able to minimize and close the page, everyting is in a
// Since the user is able to minimize and close the page, everything is in a
// client component from here.
return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const Adapter = {
return getUserById(id);
},
updateUser: async (user: AuthenticatedUser) => {
return updateUser(user.id, user);
return updateUser(user.id, { ...user, isGuest: false });
},
getUserByEmail: async (email: string) => {
return getUserByEmail(email) ?? null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,25 @@ const nextAuthOptions: AuthOptions = {

return session;
},
signIn: async ({ account, user: _user }) => {
signIn: async ({ account, user: _user, email }) => {
const session = await getServerSession(nextAuthOptions);
const sessionUser = session?.user;

if (sessionUser?.isGuest && account?.provider !== 'guest-loguin') {
// Guest account signs in with proper auth
if (
sessionUser?.isGuest &&
account?.provider !== 'guest-signin' &&
!email?.verificationRequest
) {
// Check if the user's cookie is correct
const sessionUserInDb = await getUserById(sessionUser.id);
if (!sessionUserInDb || !sessionUserInDb.isGuest) throw new Error('Something went wrong');

const user = _user as Partial<AuthenticatedUser>;
const guestUser = await getUserById(sessionUser.id);
const userSigningIn = await getUserById(_user.id);

if (guestUser?.isGuest) {
updateUser(guestUser.id, {
if (!userSigningIn) {
await updateUser(sessionUser.id, {
firstName: user.firstName ?? undefined,
lastName: user.lastName ?? undefined,
username: user.username ?? undefined,
Expand Down
2 changes: 1 addition & 1 deletion src/management-system-v2/app/shared-viewer/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const getProcessInfo = async (
const { ability, activeEnvironment } = await getCurrentEnvironment(session?.user.id);
({ spaceId } = activeEnvironment);
// get all the processes the user has access to
const ownedProcesses = await getProcesses(userId, ability);
const ownedProcesses = await getProcesses(spaceId, ability);
// check if the current user is the owner of the process(/has access to the process) => if yes give access regardless of sharing status
isOwner = ownedProcesses.some((process) => process.id === definitionId);
}
Expand Down
71 changes: 71 additions & 0 deletions src/management-system-v2/app/transfer-processes/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { getCurrentUser } from '@/components/auth';
import Content from '@/components/content';
import { getProcesses, getUserById } from '@/lib/data/DTOs';
import { Card, Result } from 'antd';
import { redirect } from 'next/navigation';
import ProcessTransferButtons from './transfer-processes-confirmation-buttons';
import { getGuestReference } from '@/lib/reference-guest-user-token';

export default async function TransferProcessesPage({
searchParams,
}: {
searchParams: {
callbackUrl?: string;
referenceToken?: string;
};
}) {
const { userId, session } = await getCurrentUser();
if (!session) redirect('api/auth/signin');
if (session.user.isGuest) redirect('/');

const callbackUrl = decodeURIComponent(searchParams.callbackUrl || '/');

const token = decodeURIComponent(searchParams.referenceToken || '');
const referenceToken = getGuestReference(token);
if ('error' in referenceToken) {
let message = 'Invalid link';
if (referenceToken.error === 'TokenExpiredError') message = 'Link expired';

return (
<Content title="Transfer Processes">
<Result
status="error"
title={message}
subTitle="If you want to transfer the processes from your guest account, you need to sign in with your email from your guest account again."
/>
</Content>
);
}
const guestId = referenceToken.guestId;

// guestId === userId if the user signed in with a non existing account, and the guest user was
// turned into an authenticated user
if (!guestId || guestId === userId) redirect(callbackUrl);

const possibleGuest = await getUserById(guestId);
// possibleGuest might be a normal user, this would happen if the user signed in with an existing
// accocunt, generating the token above, and before using it, he signed in with a new account.
// We only go further then this redirect, if the user signed in with an account that was
// already linked to an existing user
if (!possibleGuest || !possibleGuest.isGuest) redirect(callbackUrl);

// NOTE: this ignores folders
const guestProcesses = await getProcesses(guestId);

// If the guest has no processes -> nothing to do
if (guestProcesses.length === 0) redirect(callbackUrl);

return (
<Content title="Transfer Processes">
<Card
title="Would you like to transfer your processes?"
style={{ maxWidth: '70ch', margin: 'auto' }}
>
Your guest account had {guestProcesses.length} process{guestProcesses.length !== 1 && 'es'}.
<br />
Would you like to transfer them to your account?
<ProcessTransferButtons referenceToken={token} callbackUrl={callbackUrl} />
</Card>
</Content>
);
}
88 changes: 88 additions & 0 deletions src/management-system-v2/app/transfer-processes/server-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use server';

import { getCurrentUser } from '@/components/auth';
import { Folder } from '@/lib/data/folder-schema';
import {
getProcesses,
getFolders,
getRootFolder,
moveFolder,
updateFolderMetaData,
updateProcess,
getUserById,
deleteUser,
} from '@/lib/data/DTOs';
import { Process } from '@/lib/data/process-schema';
import { getGuestReference } from '@/lib/reference-guest-user-token';
import { UserErrorType, userError } from '@/lib/user-error';
import { redirect } from 'next/navigation';

export async function transferProcesses(referenceToken: string, callbackUrl: string = '/') {
const { session } = await getCurrentUser();
if (!session) return userError("You're not signed in", UserErrorType.PermissionError);
if (session.user.isGuest)
return userError("You can't be a guest to transfer processes", UserErrorType.PermissionError);

const reference = getGuestReference(referenceToken);
if ('error' in reference) return userError(reference.error);
const guestId = reference.guestId;

if (guestId === session.user.id) redirect(callbackUrl);

const possibleGuest = await getUserById(guestId);
if (!possibleGuest || !possibleGuest.isGuest)
return userError('Invalid guest id', UserErrorType.PermissionError);

// Processes and folders under root folder of guest space guet their folderId changed to the
// root folder of the new owner space, for the rest we just update the environmentId
const userRootFolderId = (await getRootFolder(session.user.id)).id;
const guestRootFolderId = (await getRootFolder(guestId)).id;

// no ability check necessary, owners of personal spaces can do anything
const guestProcesses = await getProcesses(guestId);
for (const process of guestProcesses) {
const processUpdate: Partial<Process> = {
environmentId: session.user.id,
creatorId: session.user.id,
};
if (process.folderId === guestRootFolderId) processUpdate.folderId = userRootFolderId;
await updateProcess(process.id, processUpdate);
}

const guestFolders = await getFolders(guestId);
for (const folder of guestFolders) {
if (folder.id === guestRootFolderId) continue;

const folderData: Partial<Folder> = { createdBy: session.user.id };

if (folder.parentId === guestRootFolderId) moveFolder(folder.id, userRootFolderId);
else folderData.environmentId = session.user.id;

updateFolderMetaData(folder.id, folderData);
}

deleteUser(guestId);

redirect(callbackUrl);
}

export async function discardProcesses(referenceToken: string, redirectUrl: string = '/') {
const { session } = await getCurrentUser();
if (!session) return userError("You're not signed in", UserErrorType.PermissionError);
if (session.user.isGuest)
return userError("You can't be a guest to transfer processes", UserErrorType.PermissionError);

const reference = getGuestReference(referenceToken);
if ('error' in reference) return userError(reference.error);
const guestId = reference.guestId;

if (guestId === session.user.id) redirect(redirectUrl);

const possibleGuest = await getUserById(guestId);
if (!possibleGuest || !possibleGuest.isGuest)
return userError('Invalid guest id', UserErrorType.PermissionError);

deleteUser(guestId);

redirect(redirectUrl);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use client';

import { Space, Button } from 'antd';
import { ReactNode, useTransition } from 'react';
import {
transferProcesses as serverTransferProcesses,
discardProcesses as serverDiscardProcesses,
} from './server-actions';

export default function ProcessTransferButtons({
referenceToken,
callbackUrl,
}: {
referenceToken: string;
callbackUrl?: string;
children?: ReactNode;
}) {
const [discardingProcesses, startDiscardingProcesses] = useTransition();
function discardProcesses() {
startDiscardingProcesses(async () => {
await serverDiscardProcesses(referenceToken, callbackUrl);
});
}

const [transferring, startTransfer] = useTransition();
function transferProcesses() {
startTransfer(async () => {
await serverTransferProcesses(referenceToken, callbackUrl);
});
}

return (
<Space style={{ width: '100%', justifyContent: 'right' }}>
<Button onClick={discardProcesses} loading={discardingProcesses} disabled={transferring}>
No
</Button>
<Button
type="primary"
onClick={transferProcesses}
loading={transferring}
disabled={discardingProcesses}
>
Yes
</Button>
</Space>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export async function changeEmail(token: string, identifier: string, cancel: boo
)
return userError('Invalid token');

if (!cancel) updateUser(userId, { email: verificationToken.identifier });
if (!cancel) updateUser(userId, { email: verificationToken.identifier, isGuest: false });

deleteVerificationToken(tokenParams);
}
Loading
Loading