User authentication and per-user data (#31)
Add basic user registration and login support. Each user has their own private music library. Reviewed-on: #31pull/34/head
parent
87af6e18a4
commit
f1a5597598
52 changed files with 1883 additions and 2141 deletions
@ -0,0 +1,131 @@ |
|||||||
|
import React, { useReducer } from 'react'; |
||||||
|
import { WindowState } from "../Windows"; |
||||||
|
import { Box, Paper, Typography, TextField, Button } from "@material-ui/core"; |
||||||
|
import { useHistory, useLocation } from 'react-router'; |
||||||
|
import { useAuth, Auth } from '../../../lib/useAuth'; |
||||||
|
import Alert from '@material-ui/lab/Alert'; |
||||||
|
|
||||||
|
export enum LoginStatus { |
||||||
|
NoneSubmitted = 0, |
||||||
|
Unsuccessful, |
||||||
|
// Note: no "successful" status because that would lead to a redirect.
|
||||||
|
} |
||||||
|
|
||||||
|
export interface LoginWindowState extends WindowState { |
||||||
|
email: string, |
||||||
|
password: string, |
||||||
|
status: LoginStatus, |
||||||
|
} |
||||||
|
export enum LoginWindowStateActions { |
||||||
|
SetEmail = "SetEmail", |
||||||
|
SetPassword = "SetPassword", |
||||||
|
SetStatus = "SetStatus", |
||||||
|
} |
||||||
|
export function LoginWindowReducer(state: LoginWindowState, action: any) { |
||||||
|
switch (action.type) { |
||||||
|
case LoginWindowStateActions.SetEmail: |
||||||
|
return { ...state, email: action.value } |
||||||
|
case LoginWindowStateActions.SetPassword: |
||||||
|
return { ...state, password: action.value } |
||||||
|
case LoginWindowStateActions.SetStatus: |
||||||
|
return { ...state, status: action.value } |
||||||
|
default: |
||||||
|
throw new Error("Unimplemented LoginWindow state update.") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default function LoginWindow(props: {}) { |
||||||
|
const [state, dispatch] = useReducer(LoginWindowReducer, { |
||||||
|
email: "", |
||||||
|
password: "", |
||||||
|
status: LoginStatus.NoneSubmitted, |
||||||
|
}); |
||||||
|
|
||||||
|
return <LoginWindowControlled state={state} dispatch={dispatch} /> |
||||||
|
} |
||||||
|
|
||||||
|
export function LoginWindowControlled(props: { |
||||||
|
state: LoginWindowState, |
||||||
|
dispatch: (action: any) => void, |
||||||
|
}) { |
||||||
|
let history: any = useHistory(); |
||||||
|
let location: any = useLocation(); |
||||||
|
let auth: Auth = useAuth(); |
||||||
|
let { from } = location.state || { from: { pathname: "/" } }; |
||||||
|
|
||||||
|
const onSubmit = (event: any) => { |
||||||
|
event.preventDefault(); |
||||||
|
auth.signin(props.state.email, props.state.password) |
||||||
|
.then(() => { |
||||||
|
history.replace(from); |
||||||
|
}).catch((e: any) => { |
||||||
|
props.dispatch({ |
||||||
|
type: LoginWindowStateActions.SetStatus, |
||||||
|
value: LoginStatus.Unsuccessful, |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
||||||
|
<Box |
||||||
|
m={1} |
||||||
|
mt={4} |
||||||
|
width="500px" |
||||||
|
> |
||||||
|
<Paper> |
||||||
|
<Box p={3}> |
||||||
|
<Typography variant="h5">Sign in</Typography> |
||||||
|
<form noValidate onSubmit={onSubmit}> |
||||||
|
<TextField |
||||||
|
variant="outlined" |
||||||
|
margin="normal" |
||||||
|
required |
||||||
|
fullWidth |
||||||
|
id="email" |
||||||
|
label="Email" |
||||||
|
name="email" |
||||||
|
autoFocus |
||||||
|
onInput={(e: any) => props.dispatch({ |
||||||
|
type: LoginWindowStateActions.SetEmail, |
||||||
|
value: e.target.value |
||||||
|
})} |
||||||
|
/> |
||||||
|
<TextField |
||||||
|
variant="outlined" |
||||||
|
margin="normal" |
||||||
|
required |
||||||
|
fullWidth |
||||||
|
id="password" |
||||||
|
label="Password" |
||||||
|
name="password" |
||||||
|
type="password" |
||||||
|
onInput={(e: any) => props.dispatch({ |
||||||
|
type: LoginWindowStateActions.SetPassword, |
||||||
|
value: e.target.value |
||||||
|
})} |
||||||
|
/> |
||||||
|
{props.state.status === LoginStatus.Unsuccessful && <Alert severity="error"> |
||||||
|
Login failed - Please check your credentials. |
||||||
|
</Alert> |
||||||
|
} |
||||||
|
<Button |
||||||
|
type="submit" |
||||||
|
fullWidth |
||||||
|
variant="outlined" |
||||||
|
color="primary" |
||||||
|
>Sign in</Button> |
||||||
|
<Box display="flex" alignItems="center" mt={2}> |
||||||
|
<Typography>Need an account?</Typography> |
||||||
|
<Box flexGrow={1} ml={2}><Button |
||||||
|
onClick={() => history.replace("/register")} |
||||||
|
fullWidth |
||||||
|
variant="outlined" |
||||||
|
color="primary" |
||||||
|
>Sign up</Button></Box> |
||||||
|
</Box> |
||||||
|
</form> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
} |
@ -0,0 +1,139 @@ |
|||||||
|
import React, { useReducer } from 'react'; |
||||||
|
import { WindowState } from "../Windows"; |
||||||
|
import { Box, Paper, Typography, TextField, Button } from "@material-ui/core"; |
||||||
|
import { useHistory } from 'react-router'; |
||||||
|
import { useAuth, Auth } from '../../../lib/useAuth'; |
||||||
|
import Alert from '@material-ui/lab/Alert'; |
||||||
|
import { Link } from 'react-router-dom'; |
||||||
|
|
||||||
|
export enum RegistrationStatus { |
||||||
|
NoneSubmitted = 0, |
||||||
|
Successful, |
||||||
|
Unsuccessful, |
||||||
|
} |
||||||
|
|
||||||
|
export interface RegisterWindowState extends WindowState { |
||||||
|
email: string, |
||||||
|
password: string, |
||||||
|
status: RegistrationStatus, |
||||||
|
} |
||||||
|
export enum RegisterWindowStateActions { |
||||||
|
SetEmail = "SetEmail", |
||||||
|
SetPassword = "SetPassword", |
||||||
|
SetStatus = "SetStatus", |
||||||
|
} |
||||||
|
export function RegisterWindowReducer(state: RegisterWindowState, action: any) { |
||||||
|
switch (action.type) { |
||||||
|
case RegisterWindowStateActions.SetEmail: |
||||||
|
return { ...state, email: action.value } |
||||||
|
case RegisterWindowStateActions.SetPassword: |
||||||
|
return { ...state, password: action.value } |
||||||
|
case RegisterWindowStateActions.SetStatus: |
||||||
|
return { ...state, status: action.value } |
||||||
|
default: |
||||||
|
throw new Error("Unimplemented RegisterWindow state update.") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default function RegisterWindow(props: {}) { |
||||||
|
const [state, dispatch] = useReducer(RegisterWindowReducer, { |
||||||
|
email: "", |
||||||
|
password: "", |
||||||
|
status: RegistrationStatus.NoneSubmitted, |
||||||
|
}); |
||||||
|
|
||||||
|
return <RegisterWindowControlled state={state} dispatch={dispatch} /> |
||||||
|
} |
||||||
|
|
||||||
|
export function RegisterWindowControlled(props: { |
||||||
|
state: RegisterWindowState, |
||||||
|
dispatch: (action: any) => void, |
||||||
|
}) { |
||||||
|
let history: any = useHistory(); |
||||||
|
let auth: Auth = useAuth(); |
||||||
|
|
||||||
|
const onSubmit = (event: any) => { |
||||||
|
event.preventDefault(); |
||||||
|
auth.signup(props.state.email, props.state.password) |
||||||
|
.then(() => { |
||||||
|
console.log("succes!") |
||||||
|
props.dispatch({ |
||||||
|
type: RegisterWindowStateActions.SetStatus, |
||||||
|
value: RegistrationStatus.Successful, |
||||||
|
}) |
||||||
|
}).catch((e: any) => { |
||||||
|
console.log("Fail!") |
||||||
|
props.dispatch({ |
||||||
|
type: RegisterWindowStateActions.SetStatus, |
||||||
|
value: RegistrationStatus.Unsuccessful, |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return <Box width="100%" justifyContent="center" display="flex" flexWrap="wrap"> |
||||||
|
<Box |
||||||
|
m={1} |
||||||
|
mt={4} |
||||||
|
width="500px" |
||||||
|
> |
||||||
|
<Paper> |
||||||
|
<Box p={3}> |
||||||
|
<Typography variant="h5">Sign up</Typography> |
||||||
|
<form noValidate onSubmit={onSubmit}> |
||||||
|
<TextField |
||||||
|
variant="outlined" |
||||||
|
margin="normal" |
||||||
|
required |
||||||
|
fullWidth |
||||||
|
id="email" |
||||||
|
label="Email" |
||||||
|
name="email" |
||||||
|
autoFocus |
||||||
|
onInput={(e: any) => props.dispatch({ |
||||||
|
type: RegisterWindowStateActions.SetEmail, |
||||||
|
value: e.target.value |
||||||
|
})} |
||||||
|
/> |
||||||
|
<TextField |
||||||
|
variant="outlined" |
||||||
|
margin="normal" |
||||||
|
required |
||||||
|
fullWidth |
||||||
|
id="password" |
||||||
|
label="Password" |
||||||
|
name="password" |
||||||
|
type="password" |
||||||
|
onInput={(e: any) => props.dispatch({ |
||||||
|
type: RegisterWindowStateActions.SetPassword, |
||||||
|
value: e.target.value |
||||||
|
})} |
||||||
|
/> |
||||||
|
{props.state.status === RegistrationStatus.Successful && <Alert severity="success"> |
||||||
|
Registration successful! Please {<Link to="/login">sign in</Link>} to continue. |
||||||
|
</Alert> |
||||||
|
} |
||||||
|
{props.state.status === RegistrationStatus.Unsuccessful && <Alert severity="error"> |
||||||
|
Registration failed - please check your inputs and try again. |
||||||
|
</Alert> |
||||||
|
} |
||||||
|
{props.state.status !== RegistrationStatus.Successful && <Button |
||||||
|
type="submit" |
||||||
|
fullWidth |
||||||
|
variant="outlined" |
||||||
|
color="primary" |
||||||
|
>Sign up</Button>} |
||||||
|
<Box display="flex" alignItems="center" mt={2}> |
||||||
|
<Typography>Already have an account?</Typography> |
||||||
|
<Box flexGrow={1} ml={2}><Button |
||||||
|
onClick={() => history.replace("/login")} |
||||||
|
fullWidth |
||||||
|
variant="outlined" |
||||||
|
color="primary" |
||||||
|
>Sign in</Button></Box> |
||||||
|
</Box> |
||||||
|
</form> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
} |
@ -0,0 +1,102 @@ |
|||||||
|
// Note: Based on https://usehooks.com/useAuth/
|
||||||
|
|
||||||
|
|
||||||
|
import React, { useState, useContext, createContext, ReactFragment } from "react"; |
||||||
|
import PersonIcon from '@material-ui/icons/Person'; |
||||||
|
import * as serverApi from '../api'; |
||||||
|
|
||||||
|
export interface AuthUser { |
||||||
|
id: number, |
||||||
|
email: string, |
||||||
|
icon: ReactFragment, |
||||||
|
} |
||||||
|
|
||||||
|
export interface Auth { |
||||||
|
user: AuthUser | null, |
||||||
|
signout: () => void, |
||||||
|
signin: (email: string, password: string) => Promise<AuthUser>, |
||||||
|
signup: (email: string, password: string) => Promise<void>, |
||||||
|
}; |
||||||
|
|
||||||
|
const authContext = createContext<Auth>({ |
||||||
|
user: null, |
||||||
|
signout: () => { }, |
||||||
|
signin: (email: string, password: string) => { |
||||||
|
throw new Error("Auth object not initialized."); |
||||||
|
}, |
||||||
|
signup: (email: string, password: string) => { |
||||||
|
throw new Error("Auth object not initialized."); |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
export function ProvideAuth(props: { children: any }) { |
||||||
|
const auth = useProvideAuth(); |
||||||
|
return <authContext.Provider value={auth}>{props.children}</authContext.Provider>; |
||||||
|
} |
||||||
|
|
||||||
|
export const useAuth = () => { |
||||||
|
return useContext(authContext); |
||||||
|
}; |
||||||
|
|
||||||
|
function useProvideAuth() { |
||||||
|
const [user, setUser] = useState<AuthUser | null>(null); |
||||||
|
|
||||||
|
// TODO: password maybe shouldn't be encoded into the URL.
|
||||||
|
const signin = (email: string, password: string) => { |
||||||
|
return (async () => { |
||||||
|
const urlBase = (process.env.REACT_APP_BACKEND || "") + serverApi.LoginEndpoint; |
||||||
|
const url = `${urlBase}?username=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`; |
||||||
|
|
||||||
|
const response = await fetch(url, { method: "POST" }); |
||||||
|
const json = await response.json(); |
||||||
|
if (!("userId" in json)) { |
||||||
|
throw new Error("No UserID received from login."); |
||||||
|
} |
||||||
|
|
||||||
|
const user = { |
||||||
|
id: json.userId, |
||||||
|
email: email, |
||||||
|
icon: <PersonIcon />, |
||||||
|
} |
||||||
|
setUser(user); |
||||||
|
return user; |
||||||
|
})(); |
||||||
|
}; |
||||||
|
|
||||||
|
const signup = (email: string, password: string) => { |
||||||
|
return (async () => { |
||||||
|
const requestOpts = { |
||||||
|
method: 'POST', |
||||||
|
headers: { 'Content-Type': 'application/json' }, |
||||||
|
body: JSON.stringify({ |
||||||
|
email: email, |
||||||
|
password: password, |
||||||
|
}) |
||||||
|
}; |
||||||
|
|
||||||
|
const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.RegisterUserEndpoint, requestOpts) |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error("Failed to register user.") |
||||||
|
} |
||||||
|
})(); |
||||||
|
}; |
||||||
|
|
||||||
|
const signout = () => { |
||||||
|
return (async () => { |
||||||
|
const url = (process.env.REACT_APP_BACKEND || "") + serverApi.LogoutEndpoint; |
||||||
|
const response = await fetch(url, { method: "POST" }); |
||||||
|
if (!response.ok) { |
||||||
|
throw new Error("Failed to log out."); |
||||||
|
} |
||||||
|
setUser(null); |
||||||
|
})(); |
||||||
|
}; |
||||||
|
|
||||||
|
// Return the user object and auth methods
|
||||||
|
return { |
||||||
|
user, |
||||||
|
signin, |
||||||
|
signup, |
||||||
|
signout, |
||||||
|
}; |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,49 @@ |
|||||||
|
import * as api from '../../client/src/api'; |
||||||
|
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types'; |
||||||
|
import Knex from 'knex'; |
||||||
|
|
||||||
|
import { sha512 } from 'js-sha512'; |
||||||
|
|
||||||
|
export const RegisterUserEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => { |
||||||
|
if (!api.checkRegisterUserRequest(req)) { |
||||||
|
const e: EndpointError = { |
||||||
|
internalMessage: 'Invalid RegisterUser request: ' + JSON.stringify(req.body), |
||||||
|
httpStatus: 400 |
||||||
|
}; |
||||||
|
throw e; |
||||||
|
} |
||||||
|
const reqObject: api.RegisterUserRequest = req.body; |
||||||
|
|
||||||
|
console.log("Register User: ", reqObject); |
||||||
|
|
||||||
|
await knex.transaction(async (trx) => { |
||||||
|
try { |
||||||
|
// check if the user already exists
|
||||||
|
const user = (await trx |
||||||
|
.select('id') |
||||||
|
.from('users') |
||||||
|
.where({ email: reqObject.email }))[0]; |
||||||
|
if(user) { |
||||||
|
res.status(400).send(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Create the new user.
|
||||||
|
const passwordHash = sha512(reqObject.password); |
||||||
|
const userId = (await trx('users') |
||||||
|
.insert({ |
||||||
|
email: reqObject.email, |
||||||
|
passwordHash: passwordHash, |
||||||
|
}) |
||||||
|
.returning('id') // Needed for Postgres
|
||||||
|
)[0]; |
||||||
|
|
||||||
|
// Respond to the request.
|
||||||
|
res.status(200).send(); |
||||||
|
|
||||||
|
} catch (e) { |
||||||
|
catchUnhandledErrors(e); |
||||||
|
trx.rollback(); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,73 @@ |
|||||||
|
import * as Knex from "knex"; |
||||||
|
import { sha512 } from "js-sha512"; |
||||||
|
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> { |
||||||
|
// Users table.
|
||||||
|
await knex.schema.createTable( |
||||||
|
'users', |
||||||
|
(table: any) => { |
||||||
|
table.increments('id'); |
||||||
|
table.string('email'); |
||||||
|
table.string('passwordHash') |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
// Add user column to other object tables.
|
||||||
|
await knex.schema.alterTable( |
||||||
|
'songs', |
||||||
|
(table: any) => { |
||||||
|
table.integer('user').unsigned().notNullable().defaultTo(1); |
||||||
|
} |
||||||
|
) |
||||||
|
await knex.schema.alterTable( |
||||||
|
'albums', |
||||||
|
(table: any) => { |
||||||
|
table.integer('user').unsigned().notNullable().defaultTo(1); |
||||||
|
} |
||||||
|
) |
||||||
|
await knex.schema.alterTable( |
||||||
|
'tags', |
||||||
|
(table: any) => { |
||||||
|
table.integer('user').unsigned().notNullable().defaultTo(1); |
||||||
|
} |
||||||
|
) |
||||||
|
await knex.schema.alterTable( |
||||||
|
'artists', |
||||||
|
(table: any) => { |
||||||
|
table.integer('user').unsigned().notNullable().defaultTo(1); |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> { |
||||||
|
await knex.schema.dropTable('users'); |
||||||
|
|
||||||
|
// Remove the user column
|
||||||
|
await knex.schema.alterTable( |
||||||
|
'songs', |
||||||
|
(table: any) => { |
||||||
|
table.dropColumn('user'); |
||||||
|
} |
||||||
|
) |
||||||
|
await knex.schema.alterTable( |
||||||
|
'albums', |
||||||
|
(table: any) => { |
||||||
|
table.dropColumn('user'); |
||||||
|
} |
||||||
|
) |
||||||
|
await knex.schema.alterTable( |
||||||
|
'tags', |
||||||
|
(table: any) => { |
||||||
|
table.dropColumn('user'); |
||||||
|
} |
||||||
|
) |
||||||
|
await knex.schema.alterTable( |
||||||
|
'artists', |
||||||
|
(table: any) => { |
||||||
|
table.dropColumn('user'); |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
@ -0,0 +1,145 @@ |
|||||||
|
const chai = require('chai'); |
||||||
|
const chaiHttp = require('chai-http'); |
||||||
|
const express = require('express'); |
||||||
|
import { SetupApp } from '../../../app'; |
||||||
|
import * as helpers from './helpers'; |
||||||
|
|
||||||
|
async function init() { |
||||||
|
chai.use(chaiHttp); |
||||||
|
const app = express(); |
||||||
|
const knex = await helpers.initTestDB(); |
||||||
|
|
||||||
|
SetupApp(app, knex, ''); |
||||||
|
|
||||||
|
// Login as a test user.
|
||||||
|
var agent = chai.request.agent(app); |
||||||
|
return agent; |
||||||
|
} |
||||||
|
|
||||||
|
describe('Auth registration password and email constraints', () => { |
||||||
|
it('are enforced', async done => { |
||||||
|
let req = await init(); |
||||||
|
try { |
||||||
|
await helpers.createUser(req, "someone", "password1A!", 400); //no valid email
|
||||||
|
await helpers.createUser(req, "someone@email.com", "password1A", 400); //no special char
|
||||||
|
await helpers.createUser(req, "someone@email.com", "password1!", 400); //no capital letter
|
||||||
|
await helpers.createUser(req, "someone@email.com", "passwordA!", 400); //no number
|
||||||
|
await helpers.createUser(req, "someone@email.com", "Ϭassword1A!", 400); //non-ASCII in password
|
||||||
|
await helpers.createUser(req, "Ϭomeone@email.com", "password1A!", 400); //non-ASCII in email
|
||||||
|
await helpers.createUser(req, "someone@email.com", "pass1A!", 400); //password too short
|
||||||
|
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||||
|
} finally { |
||||||
|
req.close(); |
||||||
|
done(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Attempting to register an already registered user', () => { |
||||||
|
it('should fail', async done => { |
||||||
|
let req = await init(); |
||||||
|
try { |
||||||
|
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||||
|
await helpers.createUser(req, "someone@email.com", "password1A!", 400); |
||||||
|
} finally { |
||||||
|
req.close(); |
||||||
|
done(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Auth login access for users', () => { |
||||||
|
it('is correctly enforced', async done => { |
||||||
|
let req = await init(); |
||||||
|
try { |
||||||
|
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||||
|
await helpers.createUser(req, "someoneelse@other.com", "password2B!", 200); |
||||||
|
await helpers.login(req, "someone@email.com", "password2B!", 401); |
||||||
|
await helpers.login(req, "someoneelse@other.com", "password1A!", 401); |
||||||
|
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||||
|
await helpers.login(req, "someoneelse@other.com", "password2B!", 200); |
||||||
|
} finally { |
||||||
|
req.close(); |
||||||
|
done(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Auth access to objects', () => { |
||||||
|
it('is only possible when logged in', async done => { |
||||||
|
let req = await init(); |
||||||
|
try { |
||||||
|
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||||
|
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||||
|
|
||||||
|
await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 }); |
||||||
|
await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1} ); |
||||||
|
await helpers.createAlbum(req, { name: "Album1" }, 200, { id: 1 }); |
||||||
|
await helpers.createSong(req, { title: "Song1" }, 200, { id: 1 }); |
||||||
|
|
||||||
|
await helpers.checkTag(req, 1, 200); |
||||||
|
await helpers.checkAlbum(req, 1, 200); |
||||||
|
await helpers.checkArtist(req, 1, 200); |
||||||
|
await helpers.checkSong(req, 1, 200); |
||||||
|
|
||||||
|
await helpers.logout(req, 200); |
||||||
|
|
||||||
|
await helpers.checkTag(req, 1, 401); |
||||||
|
await helpers.checkAlbum(req, 1, 401); |
||||||
|
await helpers.checkArtist(req, 1, 401); |
||||||
|
await helpers.checkSong(req, 1, 401); |
||||||
|
} finally { |
||||||
|
req.close(); |
||||||
|
done(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Auth access to user objects', () => { |
||||||
|
it('is restricted to each user', async done => { |
||||||
|
let req = await init(); |
||||||
|
try { |
||||||
|
await helpers.createUser(req, "someone@email.com", "password1A!", 200); |
||||||
|
await helpers.createUser(req, "someoneelse@other.com", "password2B!", 200); |
||||||
|
|
||||||
|
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||||
|
await helpers.createTag(req, { name: "Tag1" }, 200, { id: 1 }); |
||||||
|
await helpers.createArtist(req, { name: "Artist1" }, 200, { id: 1} ); |
||||||
|
await helpers.createAlbum(req, { name: "Album1" }, 200, { id: 1 }); |
||||||
|
await helpers.createSong(req, { title: "Song1" }, 200, { id: 1 }); |
||||||
|
await helpers.logout(req, 200); |
||||||
|
|
||||||
|
await helpers.login(req, "someoneelse@other.com", "password2B!", 200); |
||||||
|
await helpers.createTag(req, { name: "Tag2" }, 200, { id: 2 }); |
||||||
|
await helpers.createArtist(req, { name: "Artist2" }, 200, { id: 2 } ); |
||||||
|
await helpers.createAlbum(req, { name: "Album2" }, 200, { id: 2 }); |
||||||
|
await helpers.createSong(req, { title: "Song2" }, 200, { id: 2 }); |
||||||
|
await helpers.logout(req, 200); |
||||||
|
|
||||||
|
await helpers.login(req, "someone@email.com", "password1A!", 200); |
||||||
|
await helpers.checkTag(req, 2, 404); |
||||||
|
await helpers.checkAlbum(req, 2, 404); |
||||||
|
await helpers.checkArtist(req, 2, 404); |
||||||
|
await helpers.checkSong(req, 2, 404); |
||||||
|
await helpers.checkTag(req, 1, 200); |
||||||
|
await helpers.checkAlbum(req, 1, 200); |
||||||
|
await helpers.checkArtist(req, 1, 200); |
||||||
|
await helpers.checkSong(req, 1, 200); |
||||||
|
await helpers.logout(req, 200); |
||||||
|
|
||||||
|
await helpers.login(req, "someoneelse@other.com", "password2B!", 200); |
||||||
|
await helpers.checkTag(req, 1, 404); |
||||||
|
await helpers.checkAlbum(req, 1, 404); |
||||||
|
await helpers.checkArtist(req, 1, 404); |
||||||
|
await helpers.checkSong(req, 1, 404); |
||||||
|
await helpers.checkTag(req, 2, 200); |
||||||
|
await helpers.checkAlbum(req, 2, 200); |
||||||
|
await helpers.checkArtist(req, 2, 200); |
||||||
|
await helpers.checkSong(req, 2, 200); |
||||||
|
await helpers.logout(req, 200); |
||||||
|
} finally { |
||||||
|
req.close(); |
||||||
|
done(); |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue