Skip to content

Commit

Permalink
feat: add authorization and challenge code
Browse files Browse the repository at this point in the history
  • Loading branch information
solufa committed Sep 14, 2024
1 parent 6187577 commit 16b178c
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 34 deletions.
23 changes: 23 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"@aspida/axios": "^1.14.0",
"@aspida/swr": "^1.14.0",
"@aws-amplify/ui-react": "^6.3.0",
"@aws-sdk/client-cognito-identity-provider": "^3.645.0",
"@fakerjs/word": "^1.0.1",
Expand All @@ -29,6 +30,7 @@
"next": "^14.2.9",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"swr": "^2.2.5",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
6 changes: 3 additions & 3 deletions client/pages/_app.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ function MyApp({ Component, pageProps }: AppProps) {
oauth: {
domain: 'localhost:5052',
scopes: ['openid'],
redirectSignIn: ['http://localhost:5051'],
redirectSignOut: ['http://localhost:5051'],
responseType: 'token',
redirectSignIn: [location.origin],
redirectSignOut: [location.origin],
responseType: 'code',
},
},
},
Expand Down
2 changes: 2 additions & 0 deletions client/pages/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { useEffect } from 'react';
import { pagesPath } from 'utils/$path';
import styles from './index.module.css';

export type OptionalQuery = { code: string; state: string };

const Home = () => {
const { user } = useUser();
const router = useRouter();
Expand Down
36 changes: 36 additions & 0 deletions client/pages/oauth2/authorize.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,37 @@
margin: 0 auto;
}

.userInfo {
display: flex;
gap: 16px;
align-items: center;
padding: 12px 8px;
color: gray;
cursor: pointer;
border-radius: 6px;
transition: 0.2s;
}

.userInfo:hover {
background: #eee;
}

.userName {
font-weight: bold;
}

.userEmail {
font-size: 14px;
}

.userIcon {
width: 36px;
height: 36px;
background: #aaa;
background-size: cover;
border-radius: 50%;
}

.btn {
padding: 6px 16px;
font-weight: bold;
Expand All @@ -14,6 +45,11 @@
border-radius: 6px;
}

.desc {
font-size: 14px;
color: gray;
}

.inputLabel {
font-size: 14px;
color: #777;
Expand Down
86 changes: 76 additions & 10 deletions client/pages/oauth2/authorize.page.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,74 @@
import useAspidaSWR from '@aspida/swr';
import type { OAuthConfig } from '@aws-amplify/core';
import word from '@fakerjs/word';
import { PROVIDER_LIST } from 'common/constants';
import { APP_NAME, PROVIDER_LIST } from 'common/constants';
import type { MaybeId } from 'common/types/brandedId';
import { Spacer } from 'components/Spacer';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { apiClient } from 'utils/apiClient';
import { z } from 'zod';
import styles from './authorize.module.css';

export type Query = {
redirect_uri: string;
response_type: OAuthConfig['responseType'];
client_id: MaybeId['userPoolClient'];
identity_provider: string;
scope: OAuthConfig['scopes'];
state: string;
};
} & (
| { response_type: 'code'; code_challenge: string; code_challenge_method: 'plain' | 'S256' }
| { response_type: 'token' }
);

const AddAccount = (props: { provider: string; onBack: () => void }) => {
const AddAccount = (props: {
provider: (typeof PROVIDER_LIST)[number];
codeChallenge: string;
userPoolClientId: MaybeId['userPoolClient'];
onBack: () => void;
}) => {
const [email, setEmail] = useState('');
const [displayName, setDisplayName] = useState('');
const [photoUrl, setPhotoUrl] = useState('');
const data = z
.object({
email: z.string().email(),
displayName: z.string(),
name: z.string(),
photoUrl: z.literal('').or(z.string().url().optional()),
})
.safeParse({ email, displayName, photoUrl });
.safeParse({ email, name: displayName, photoUrl });
const setFakeVals = () => {
const fakeWord = word({ length: 8 });

setEmail(`${fakeWord}@${props.provider.toLowerCase()}.com`);
setDisplayName(fakeWord);
};
const addUser = async () => {
if (!data.success) return;

await apiClient.public.socialUsers.$post({
body: {
...data.data,
photoUrl: photoUrl || undefined,
provider: props.provider,
codeChallenge: props.codeChallenge,
userPoolClientId: props.userPoolClientId,
},
});
};

return (
<div>
<button className={styles.btn} onClick={setFakeVals}>
Auto-generate user information
</button>
<Spacer axis="y" size={20} />
<form>
<form
onSubmit={(e) => {
e.preventDefault();
addUser();
}}
>
<div className={styles.inputLabel}>Email</div>
<Spacer axis="y" size={4} />
<input
Expand Down Expand Up @@ -82,25 +109,64 @@ const AddAccount = (props: { provider: string; onBack: () => void }) => {
);
};

// eslint-disable-next-line complexity
const Authorize = () => {
const router = useRouter();
const userPoolClientId = router.query.client_id as MaybeId['userPoolClient'];
const codeChallenge = router.query.code_challenge as string;
const provider = z
.enum(PROVIDER_LIST)
.parse((router.query.identity_provider as string).replace(/^.+([A-Z][a-z]+)$/, '$1'));
.parse(
(router.query.identity_provider as string | undefined)?.replace(/^.+([A-Z][a-z]+)$/, '$1') ??
'Google',
);
const { data: users } = useAspidaSWR(apiClient.public.socialUsers, {
query: { userPoolClientId },
});
const [mode, setMode] = useState<'default' | 'add'>('default');

return (
<div className={styles.container}>
<h1>Sign-in with {provider}</h1>
<Spacer axis="y" size={8} />
<div>No {provider} accounts exist in Magnito.</div>
{users && users.length > 0 ? (
<>
<div className={styles.desc}>Please select an existing account or add a new one.</div>
<Spacer axis="y" size={16} />
{users.map((user) => (
<div key={user.id} className={styles.userInfo}>
<div
className={styles.userIcon}
style={{
backgroundImage: user.attributes.some((attr) => attr.name === 'picture')
? `url(${user.attributes.find((attr) => attr.name === 'picture')?.value})`
: undefined,
}}
/>
<div>
<div className={styles.userName}>{user.name}</div>
<div className={styles.userEmail}>{user.email}</div>
</div>
</div>
))}
</>
) : (
<div className={styles.desc}>
No {provider} accounts exist in {APP_NAME}.
</div>
)}
<Spacer axis="y" size={20} />
{mode === 'default' ? (
<button className={styles.btn} onClick={() => setMode('add')}>
+ Add new account
</button>
) : (
<AddAccount provider={provider} onBack={() => setMode('default')} />
<AddAccount
provider={provider}
codeChallenge={codeChallenge}
userPoolClientId={userPoolClientId}
onBack={() => setMode('default')}
/>
)}
</div>
);
Expand Down
1 change: 1 addition & 0 deletions server/api/public/socialUsers/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default defineController(() => ({
provider: z.enum(PROVIDER_LIST),
name: z.string(),
email: z.string(),
codeChallenge: z.string(),
photoUrl: z.string().optional(),
userPoolClientId: brandedId.userPoolClient.maybe,
}),
Expand Down
5 changes: 5 additions & 0 deletions server/common/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export type SocialUserEntity = {
provider: (typeof PROVIDER_LIST)[number];
password?: undefined;
confirmationCode?: undefined;
authorizationCode: string;
codeChallenge: string;
salt?: undefined;
verifier?: undefined;
refreshToken: string;
Expand All @@ -45,6 +47,8 @@ export type CognitoUserEntity = {
provider?: undefined;
password: string;
confirmationCode: string;
authorizationCode?: undefined;
codeChallenge?: undefined;
salt: string;
verifier: string;
refreshToken: string;
Expand All @@ -61,6 +65,7 @@ export type SocialUserCreateVal = {
provider: (typeof PROVIDER_LIST)[number];
name: string;
email: string;
codeChallenge: string;
photoUrl?: string;
userPoolClientId: MaybeId['userPoolClient'];
};
2 changes: 2 additions & 0 deletions server/domain/user/model/socialUserMethod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const socialUserMethod = {
kind: 'social',
email: val.email,
provider: val.provider,
codeChallenge: val.codeChallenge,
authorizationCode: ulid(),
enabled: true,
status: 'EXTERNAL_PROVIDER',
name: val.name,
Expand Down
2 changes: 2 additions & 0 deletions server/domain/user/repository/toUserEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export const toSocialUserEntity = (
status: z.literal(UserStatusType.EXTERNAL_PROVIDER).parse(prismaUser.status),
email: prismaUser.email,
provider: z.enum(PROVIDER_LIST).parse(prismaUser.provider),
authorizationCode: z.string().parse(prismaUser.authorizationCode),
codeChallenge: z.string().parse(prismaUser.codeChallenge),
refreshToken: prismaUser.refreshToken,
userPoolId: brandedId.userPool.entity.parse(prismaUser.userPoolId),
attributes: prismaUser.attributes.map(
Expand Down
4 changes: 4 additions & 0 deletions server/domain/user/repository/userCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const userCommand = {
verifier: user.verifier,
refreshToken: user.refreshToken,
confirmationCode: user.confirmationCode,
authorizationCode: user.authorizationCode,
codeChallenge: user.codeChallenge,
secretBlock: user.challenge?.secretBlock,
pubA: user.challenge?.pubA,
pubB: user.challenge?.pubB,
Expand All @@ -40,6 +42,8 @@ export const userCommand = {
verifier: user.verifier,
refreshToken: user.refreshToken,
confirmationCode: user.confirmationCode,
authorizationCode: user.authorizationCode,
codeChallenge: user.codeChallenge,
userPoolId: user.userPoolId,
attributes: { createMany: { data: user.attributes } },
createdAt: new Date(user.createdTime),
Expand Down
3 changes: 3 additions & 0 deletions server/prisma/migrations/20240914025426_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "authorizationCode" TEXT;
ALTER TABLE "User" ADD COLUMN "codeChallenge" TEXT;
Loading

0 comments on commit 16b178c

Please sign in to comment.