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

Security section routing + set first password #65

Merged
merged 25 commits into from Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.adoc
Expand Up @@ -75,8 +75,8 @@ mkdir certs
Add the local FQDN in /etc/hosts

----
echo "127.0.0.1 sparta.fewlines.local front.sparta.fewlines.local slidedeck.sparta.fewlines.local" | sudo tee -a /etc/hosts
echo "::1 sparta.fewlines.local front.sparta.fewlines.local slidedeck.sparta.fewlines.local" | sudo tee -a /etc/hosts
echo "127.0.0.1 connect-account.local" | sudo tee -a /etc/hosts
echo "::1 connect-account.local" | sudo tee -a /etc/hosts
----

=== Development Process
Expand Down
38 changes: 38 additions & 0 deletions lib/@types/Password.ts
@@ -0,0 +1,38 @@
export type ProviderUserPasswordSet = {
id: string;
name: string;
user: {
id: string;
passwords: {
available: boolean;
};
};
};

export type SetPasswordErrorRules = {
min_digits: {
error: boolean;
minimum: number;
};
min_non_digits: {
error: boolean;
minimum: number;
};
min_total_characters: {
error: boolean;
minimum: number;
};
};

export type SetPasswordError = {
code: string;
locations: [
{
column: number;
line: number;
},
];
message: string;
path: string[];
rules: SetPasswordErrorRules;
};
9 changes: 9 additions & 0 deletions lib/@types/ProviderUser.ts
Expand Up @@ -17,3 +17,12 @@ export type SingleIdentityProviderUser = {
identity: Identity | null;
};
};

export type User = {
id: string;
};

export type CreateOrUpdatePasswordInput = {
cleartextPassword: string;
userId: string;
};
32 changes: 32 additions & 0 deletions lib/commands/createOrUpdatePassword.ts
@@ -0,0 +1,32 @@
import type { FetchResult } from "apollo-link";
import gql from "graphql-tag";

import { CreateOrUpdatePasswordInput, User } from "@lib/@types/ProviderUser";
import { fetchManagement } from "@src/utils/fetchManagement";

export type CreateOrUpdatePassword = Promise<
FetchResult<{
createOrUpdatePassword: User;
}>
>;

const CREATE_OR_UPDATE_PASSWORD_MUTATION = gql`
mutation createOrUpdatePassword($cleartextPassword: String!, $userId: ID!) {
createOrUpdatePassword(
input: { cleartextPassword: $cleartextPassword, userId: $userId }
) {
id
}
}
`;

export async function createOrUpdatePassword(
command: CreateOrUpdatePasswordInput,
): CreateOrUpdatePassword {
const operation = {
query: CREATE_OR_UPDATE_PASSWORD_MUTATION,
variables: command,
};

return fetchManagement(operation) as CreateOrUpdatePassword;
}
33 changes: 33 additions & 0 deletions lib/queries/isUserPasswordSet.ts
@@ -0,0 +1,33 @@
import type { FetchResult } from "apollo-link";
import gql from "graphql-tag";

import type { ProviderUserPasswordSet } from "@lib/@types/Password";
import { fetchManagement } from "@src/utils/fetchManagement";

const IS_USER_PASSWORD_SET_QUERY = gql`
query isUserPasswordSet($userId: String!) {
provider {
id
name
user(filters: { userId: $userId }) {
id
passwords {
available
}
}
}
}
`;

export async function isUserPasswordSet(
userId: string,
): Promise<FetchResult<{ provider: ProviderUserPasswordSet }>> {
const operation = {
query: IS_USER_PASSWORD_SET_QUERY,
variables: { userId },
};

return fetchManagement(operation) as FetchResult<{
provider: ProviderUserPasswordSet;
}>;
}
2 changes: 2 additions & 0 deletions src/clientErrors.ts
Expand Up @@ -2,4 +2,6 @@ export class ErrorSendingValidationCode extends Error {}

export class ErrorVerifyingValidationCode extends Error {}

export class ErrorSettingPassword extends Error {}

export class NoIdentityFound extends Error {}
43 changes: 17 additions & 26 deletions src/components/Layout.tsx
@@ -1,4 +1,3 @@
import Head from "next/head";
import { useRouter } from "next/router";
import React from "react";
import styled from "styled-components";
Expand All @@ -16,36 +15,28 @@ export const Layout: React.FC = ({ children }) => {
const router = useRouter();

return (
<>
<Head>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<title>Connect Account</title>
</Head>
<Main>
{router && router.pathname !== "/" && (
<MobileDisplayOnly>
<Header />
</MobileDisplayOnly>
)}
<Flex>
{router && router.pathname !== "/" && (
<DesktopNavigationBarWrapper>
<DesktopNavigationBar />
</DesktopNavigationBarWrapper>
)}
<ChildrenContainer>{children}</ChildrenContainer>
</Flex>
{router && router.pathname !== "/" && isMobileNavBarOpen && (
<ClickAwayListener onClick={() => setIsMobileNavbarOpen(false)} />
)}
{router && router.pathname !== "/" && (
<Main>
{router && router.pathname !== "/" && (
<MobileDisplayOnly>
<Header />
<MobileNavigationBar
isOpen={isMobileNavBarOpen}
setIsOpen={setIsMobileNavbarOpen}
/>
{isMobileNavBarOpen && (
<ClickAwayListener onClick={() => setIsMobileNavbarOpen(false)} />
)}
</MobileDisplayOnly>
)}
<Flex>
{router && router.pathname !== "/" && (
<DesktopNavigationBarWrapper>
<DesktopNavigationBar />
</DesktopNavigationBarWrapper>
)}
</Main>
</>
<ChildrenContainer>{children}</ChildrenContainer>
</Flex>
</Main>
);
};

Expand Down
38 changes: 38 additions & 0 deletions src/components/business/SetPassword.tsx
@@ -0,0 +1,38 @@
import React from "react";

import type { SetPasswordError } from "@lib/@types/Password";
import type { User } from "@lib/@types/ProviderUser";
import { HttpVerbs } from "@src/@types/HttpVerbs";
import { ErrorSettingPassword } from "@src/clientErrors";
import { fetchJson } from "@src/utils/fetchJson";

interface SetPasswordProps {
children: (props: {
setPassword: (passwordInput: string) => Promise<SetPasswordOutput>;
}) => JSX.Element;
}

export type SetPasswordOutput = {
data: { createOrUpdatePassword: User };
restrictionRulesError?: SetPasswordError;
};

export const SetPassword: React.FC<SetPasswordProps> = ({ children }) => {
async function setPassword(
passwordInput: string,
): Promise<SetPasswordOutput> {
return fetchJson("/api/auth-connect/set-password", HttpVerbs.POST, {
passwordInput,
}).then((response) => {
if (response.status >= 400 && response.status !== 422) {
Fuzznimp marked this conversation as resolved.
Show resolved Hide resolved
throw new ErrorSettingPassword();
}

return response.json();
});
}

return children({
setPassword,
});
};
@@ -1,7 +1,7 @@
import React from "react";

import { StoriesContainer } from "../StoriesContainer";
import AccountOverview from "./AccountOverview";
import { AccountOverview } from "./AccountOverview";

export default { title: "pages/Account Overview", component: AccountOverview };

Expand Down
75 changes: 25 additions & 50 deletions src/components/display/fewlines/AccountOverview/AccountOverview.tsx
@@ -1,59 +1,34 @@
import Link from "next/link";
import React from "react";
import styled from "styled-components";

import { LoginsIcon } from "../Icons/LoginsIcon/LoginsIcon";
import { RightChevron } from "../Icons/RightChevron/RightChevron";
import { NeutralLink } from "../NeutralLink/NeutralLink";
import { ShadowBox } from "../ShadowBox/ShadowBox";
import { SecurityIcon } from "../Icons/SecurityIcon/SecurityIcon";
import { SectionListItem } from "../SectionListItem/SectionListItem";

const AccountOverview: React.FC = () => {
export const SECTION_LIST_CONTENT = {
LOGINS: {
text:
"Manage your logins options, including emails, phone numbers and social logins",
icon: <LoginsIcon />,
},
SECURITY: {
text:
"Set or change your password. You can check your connections history here",
icon: <SecurityIcon />,
},
};

export const AccountOverview: React.FC = () => {
return (
<>
<ShadowBox>
<Content>
<Link href="/account/logins">
<NeutralLink>
<Flex>
<LoginsIcon />
<TextBox>
<Span>LOGINS</Span>
<div>
Manage your logins options, including emails, phone numbers
and social logins
</div>
</TextBox>
<RightChevron />
</Flex>
</NeutralLink>
</Link>
</Content>
</ShadowBox>
{Object.entries(SECTION_LIST_CONTENT).map(([sectionName, content]) => {
return (
<SectionListItem
key={sectionName}
sectionName={sectionName}
content={content}
/>
);
})}
</>
);
};

export default AccountOverview;

const Content = styled.div`
cursor: pointer;
`;

const Flex = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding: ${({ theme }) => theme.spaces.xs};
`;

export const TextBox = styled.div`
display: flex;
flex-direction: column;
max-width: 50%;
font-size: ${({ theme }) => theme.fontSizes.xxs};
line-height: ${({ theme }) => theme.lineHeights.title};
`;

export const Span = styled.p`
font-size: ${({ theme }) => theme.fontSizes.s};
`;
Expand Up @@ -26,7 +26,9 @@ export const DesktopNavigationBar: React.FC = () => {
</ListItem>
<ListItem>
<LockIcon />
<ListLabel>Security</ListLabel>
<ListLabel onClick={() => router.push("/account/security")}>
Fuzznimp marked this conversation as resolved.
Show resolved Hide resolved
Security
</ListLabel>
</ListItem>
<Separator />
<SwitchLanguageItem onClick={() => router.push("/account/locale")}>
Expand Down
26 changes: 13 additions & 13 deletions src/components/display/fewlines/Header/Header.tsx
Expand Up @@ -7,25 +7,25 @@ export const Header: React.FC = () => {
return (
<Flex>
<img width="90" src={fewlinesLogo} aria-label="Fewlines logo" />
<H5>Account</H5>
<p>Account</p>
</Flex>
);
};

const Flex = styled.div`
display: flex;
align-items: center;
margin: ${({ theme }) => theme.spaces.xs};
`;
margin: ${({ theme }) => `${theme.spaces.xs} ${theme.spaces.xs}`};

const H5 = styled.h5`
padding-left: ${({ theme }) => theme.spaces.xxs};
margin-left: ${({ theme }) => theme.spaces.xxs};
border-left: ${({ theme }) =>
`${theme.colors.blacks[0]} ${theme.borders.thin}`};
font-weight: ${({ theme }) => theme.fontWeights.normal};
font-size: ${({ theme }) => theme.fontSizes.s};
display: flex;
align-items: center;
height: ${({ theme }) => theme.spaces.s};
p {
Fuzznimp marked this conversation as resolved.
Show resolved Hide resolved
display: flex;
align-items: center;
height: ${({ theme }) => theme.spaces.s};
padding-left: ${({ theme }) => theme.spaces.xxs};
margin-left: ${({ theme }) => theme.spaces.xxs};
border-left: ${({ theme }) =>
`${theme.colors.blacks[0]} ${theme.borders.thin}`};
font-weight: ${({ theme }) => theme.fontWeights.normal};
font-size: ${({ theme }) => theme.fontSizes.s};
}
`;
2 changes: 1 addition & 1 deletion src/components/display/fewlines/Home/Home.stories.tsx
@@ -1,7 +1,7 @@
import React from "react";

import { StoriesContainer } from "../StoriesContainer";
import Home from "./Home";
import { Home } from "./Home";

export default { title: "pages/Home", component: Home };

Expand Down