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 19 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 { 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 { 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 {}
38 changes: 38 additions & 0 deletions src/components/business/SetPassword.tsx
@@ -0,0 +1,38 @@
import React from "react";

import { SetPasswordError } from "@lib/@types/Password";
import { 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,
});
};
71 changes: 24 additions & 47 deletions src/components/display/fewlines/Account/Account.tsx
@@ -1,64 +1,41 @@
import Link from "next/link";
import React from "react";
import styled from "styled-components";

import { Container } from "../Container";
import { H1 } from "../H1/H1";
import { H2 } from "../H2/H2";
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";

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 />,
},
};

const Account: React.FC = () => {
return (
<Container>
<H1>Welcome to your account</H1>
<H2>First name last name</H2>
<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}
/>
);
})}
</Container>
);
};

export default Account;

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};
}
`;
Expand Up @@ -2,11 +2,8 @@ import React from "react";

import { SecurityIcon } from "./SecurityIcon";

export default {
title: "icons/SecurityIcon",
component: SecurityIcon,
};
export default { title: "icons/SecurityIcon", component: SecurityIcon };

export const StandardlSecurityIcon = (): JSX.Element => {
export const StandardSecurityIcon = (): JSX.Element => {
return <SecurityIcon />;
};
Expand Up @@ -53,7 +53,11 @@ export const MobileNavigationBar: React.FC<MobileNavigationBarProp> = ({
</ListItemLabel>
<RightChevron />
</ListItem>
<ListItem>
<ListItem
onClick={() =>
router.push("/account/security").then(() => setIsOpen(false))
Fuzznimp marked this conversation as resolved.
Show resolved Hide resolved
}
>
<ListItemLabel>
<LockIcon />
<Value>Security</Value>
Expand Down
@@ -0,0 +1,55 @@
import React from "react";

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

export default {
title: "components/PasswordRulesErrorList",
component: PasswordRulesErrorList,
};

const singularRules = {
min_digits: {
error: true,
minimum: 1,
},
min_non_digits: {
error: false,
minimum: 1,
},
min_total_characters: {
error: true,
minimum: 1,
},
};

const pluralRules = {
min_digits: {
error: true,
minimum: 6,
},
min_non_digits: {
error: false,
minimum: 6,
},
min_total_characters: {
error: true,
minimum: 6,
},
};

export const PasswordRulesErrorListSingular = (): JSX.Element => {
return (
<StoriesContainer>
<PasswordRulesErrorList rules={singularRules} />
</StoriesContainer>
);
};

export const PasswordRulesErrorListPlural = (): JSX.Element => {
return (
<StoriesContainer>
<PasswordRulesErrorList rules={pluralRules} />
</StoriesContainer>
);
};