Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ MAIL_SEND_WELCOME_EMAIL=1
DEFAULT_PROFILE_IMAGE=

AUTH_PASSWORD_RESET_LIFETIME=1800

AUTH_PASSWORD_SHAPE_PATTERN="^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-])"
AUTH_PASSWORD_SHAPE_WARNING="Password must include at least one uppercase letter, one lowercase letter, one number, and one special character."

Expand Down
50 changes: 50 additions & 0 deletions LOCAL_DEVELOPMENT_HOWTO.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,56 @@ SSL_ENABLED=false
[https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-22-04](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-22-04) and [https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-22-04](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-22-04)
4.Run script ./start_local_server.sh (http://localhost:8001/)

Frontend Development
====================

openstackid is a Laravel app — blade templates reference compiled JS bundles in `public/assets/`, not source files. The source files in `resources/js/` must be compiled before changes take effect in the browser.

**One-time setup:** The compiled assets in `public/assets/` are owned by root if they were originally built inside Docker. Fix ownership before running any build commands:

```bash
sudo chown -R $(whoami) public/assets
```

**Build and watch** (recompiles automatically on save, Ctrl+C to stop):

```bash
npm run build-dev
```

To do a one-shot build without watch mode:

```bash
./node_modules/.bin/webpack --config webpack.prod.js
```

**Captcha:** The reset password and other pages require a Cloudflare Turnstile site key. Add test keys to `.env` to use the captcha locally without a real Cloudflare account:

```dotenv
TURNSTILE_SITE_KEY=1x00000000000000000000AA
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA
```

**Email:** Outbound email is logged to a date-stamped file in `storage/logs/` rather than sent. However, emails are queued via Redis by default, so they are not processed until a queue worker runs. After submitting the forgot password form, process the queue to trigger the log entry:

```bash
docker exec idp-app php artisan queue:work --stop-when-empty
```

This processes all pending jobs and exits when the queue is empty. Use `--once` if you only want to process a single job.

To retrieve a password reset link after processing the queue:

```bash
grep "password/reset" storage/logs/laravel-$(date +%Y-%m-%d).log | tail -1
```

After editing `.env`, clear the config cache:

```bash
docker exec idp-app php artisan config:clear
```

Redump the database
===================

Expand Down
4 changes: 3 additions & 1 deletion config/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,13 @@

// in seconds
'password_reset_lifetime' => env('AUTH_PASSWORD_RESET_LIFETIME', 1800),
'password_min_length' => env('AUTH_PASSWORD_MIN_LENGTH', 8),
'password_min_length' => env('AUTH_PASSWORD_MIN_LENGTH', 10),
'password_max_length' => env('AUTH_PASSWORD_MAX_LENGTH', 30),
'password_allowed_special_characters' => env('AUTH_PASSWORD_ALLOWED_SPECIAL_CHARACTERS', '[A-Za-z0-9#?!@$%^&*+-]'),
'password_allowed_special_characters_text' => env('AUTH_PASSWORD_ALLOWED_SPECIAL_CHARACTERS_TEXT', '#?!@$%^&*+-'),
'password_shape_pattern' => env('AUTH_PASSWORD_SHAPE_PATTERN', '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*+\-])[A-Za-z0-9#?!@$%^&*+\-]+$'),
'password_shape_warning' => env('AUTH_PASSWORD_SHAPE_WARNING', 'Password must include at least one uppercase letter, one lowercase letter, one number, and one special character (#?!@$%^&*+-).'),
'password_shape_list' => env('AUTH_PASSWORD_SHAPE_LIST', 'Uppercase letter,Lowercase letter,One number,Special character'),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
'verification_email_lifetime' => env("AUTH_VERIFICATION_EMAIL_LIFETIME", 600),
'allows_native_auth' => env('AUTH_ALLOWS_NATIVE_AUTH', 1),
'allows_native_on_config' => env('AUTH_ALLOWS_NATIVE_AUTH_CONFIG', 1),
Expand Down
14 changes: 7 additions & 7 deletions database/seeds/TestSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ private function createTestUsers(){
'first_name' => 'Sebastian',
'last_name' => 'Marcet',
'email' => '[email protected]',
'password' => '1Qaz2wsx!',
'password' => '1Qaz2wsx!9',
'password_enc' => \Auth\AuthHelper::AlgSHA1_V2_4,
'gender' => 'male',
'address1' => 'Av. Siempre Viva 111',
Expand All @@ -269,7 +269,7 @@ private function createTestUsers(){
'first_name' => 'Sebastian',
'last_name' => 'Marcet IDN',
'email' => 'hei@やる.ca',
'password' => '1Qaz2wsx!',
'password' => '1Qaz2wsx!9',
'password_enc' => \Auth\AuthHelper::AlgSHA1_V2_4,
'gender' => 'male',
'address1' => 'Av. Siempre Viva 111',
Expand All @@ -290,7 +290,7 @@ private function createTestUsers(){
'first_name' => 'Márton',
'last_name' => 'Kiss',
'email' => '[email protected]',
'password' => '1Qaz2wsx!',
'password' => '1Qaz2wsx!9',
'password_enc' => \Auth\AuthHelper::AlgSHA1_V2_4,
'gender' => 'male',
'address1' => 'Av. Siempre Viva 111',
Expand All @@ -311,7 +311,7 @@ private function createTestUsers(){
'first_name' => '付',
'last_name' => '金刚',
'email' => '[email protected]',
'password' => '1Qaz2wsx!',
'password' => '1Qaz2wsx!9',
'password_enc' => \Auth\AuthHelper::AlgSHA1_V2_4,
'gender' => 'male',
'address1' => 'Av. Siempre Viva 111',
Expand All @@ -332,7 +332,7 @@ private function createTestUsers(){
'first_name' => 'Bharath',
'last_name' => 'Kumar M R',
'email' => '[email protected]',
'password' => '1Qaz2wsx!',
'password' => '1Qaz2wsx!9',
'password_enc' => \Auth\AuthHelper::AlgSHA1_V2_4,
'gender' => 'male',
'address1' => 'Av. Siempre Viva 111',
Expand All @@ -353,7 +353,7 @@ private function createTestUsers(){
'first_name' => '大塚',
'last_name' => '元央',
'email' => '[email protected]',
'password' => '1Qaz2wsx!',
'password' => '1Qaz2wsx!9',
'password_enc' => \Auth\AuthHelper::AlgSHA1_V2_4,
'gender' => 'male',
'address1' => 'Av. Siempre Viva 111',
Expand All @@ -374,7 +374,7 @@ private function createTestUsers(){
'first_name' => 'Ian Y.',
'last_name' => 'Choi',
'email' => '[email protected]',
'password' => '1Qaz2wsx!',
'password' => '1Qaz2wsx!9',
'password_enc' => \Auth\AuthHelper::AlgSHA1_V2_4,
'gender' => 'male',
'address1' => 'Av. Siempre Viva 111',
Expand Down
79 changes: 67 additions & 12 deletions resources/js/reset_password/reset_password.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import CardContent from "@material-ui/core/CardContent";
import Container from "@material-ui/core/Container";
import CssBaseline from "@material-ui/core/CssBaseline";
import Grid from "@material-ui/core/Grid";
import IconButton from "@material-ui/core/IconButton";
import InputAdornment from "@material-ui/core/InputAdornment";
import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined";
import Visibility from "@material-ui/icons/Visibility";
import VisibilityOff from "@material-ui/icons/VisibilityOff";
import CheckCircle from "@material-ui/icons/CheckCircle";
import Cancel from "@material-ui/icons/Cancel";
import RadioButtonUnchecked from "@material-ui/icons/RadioButtonUnchecked";
import PasswordStrengthBar from "react-password-strength-bar";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
Expand Down Expand Up @@ -39,6 +46,7 @@ const ResetPasswordPage = ({
const formEl = useRef(null);
const captcha = useRef(null);
const [captchaConfirmation, setCaptchaConfirmation] = useState(null);
const [showPassword, setShowPassword] = useState(false);

useEffect(() => {
if (resetPasswordError) {
Expand Down Expand Up @@ -76,6 +84,8 @@ const ResetPasswordPage = ({
}
};

const passwordRequirementItems = passwordPolicy.shape_list.split(",").map(s => s.trim());

return (
<Container component="main" maxWidth="xs" className={styles.main_container}>
<CssBaseline />
Expand All @@ -94,8 +104,8 @@ const ResetPasswordPage = ({
>
<Card className={styles.reset_password_container} variant="outlined">
<CardHeader
title="Reset your Password"
subheader="You can reset your password here."
title="Set a new password."
subheader="Resetting the password for the FNid below."
/>
<CardContent>
<Grid
Expand All @@ -106,33 +116,49 @@ const ResetPasswordPage = ({
>
<Grid item>
<TextField
className={styles.email_field}
id="email"
name="email"
autoComplete="email"
variant="outlined"
variant="filled"
fullWidth
size="small"
label="Email Address"
hiddenLabel
value={formik.values.email}
disabled={true}
InputProps={{ disableUnderline: true }}
/>
</Grid>
<Grid item xs={12}>
<TextField
id="password"
name="password"
type="password"
type={showPassword ? "text" : "password"}
variant="outlined"
fullWidth
size="small"
label="Password"
label="New password"
inputProps={{maxLength: passwordPolicy.max_length}}
value={formik.values.password}
onChange={formik.handleChange}
error={
formik.touched.password && Boolean(formik.errors.password)
}
helperText={formik.touched.password && formik.errors.password}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
size="small"
aria-label={showPassword ? "Hide password" : "Show password"}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</InputAdornment>
)
}}
/>
{formik.values.password && (
<PasswordStrengthBar
Expand All @@ -149,7 +175,7 @@ const ResetPasswordPage = ({
variant="outlined"
fullWidth
size="small"
label="Confirm Password"
label="Confirm password"
inputProps={{maxLength: passwordPolicy.max_length}}
value={formik.values.password_confirmation}
onChange={formik.handleChange}
Expand All @@ -161,14 +187,37 @@ const ResetPasswordPage = ({
formik.touched.password_confirmation &&
formik.errors.password_confirmation
}
InputProps={{
endAdornment: (
<InputAdornment position="end">
{!formik.values.password_confirmation
? <RadioButtonUnchecked style={{ color: '#ccc' }} />
: formik.values.password_confirmation === formik.values.password && formik.values.password
? <CheckCircle style={{ color: '#2e7d32' }} />
: <Cancel style={{ color: '#c62828', opacity: 0.6 }} />
}
<span className={styles.sr_only} aria-live="polite">
{!formik.values.password_confirmation
? ""
: formik.values.password_confirmation === formik.values.password && formik.values.password
? "Passwords match"
: "Passwords do not match"
}
</span>
</InputAdornment>
)
}}
/>
</Grid>
<Grid item className={styles.password_hint}>
<InfoOutlinedIcon fontSize="small"/>
&nbsp;
<Typography variant="body2">
<div dangerouslySetInnerHTML={{ __html: `The Password must be ${passwordPolicy.min_length}–${passwordPolicy.max_length} characters, and ${passwordPolicy.shape_warning}` }} />
</Typography>
<p>Your password must include:</p>
<ul>
<li key="length">{passwordPolicy.min_length}–{passwordPolicy.max_length} characters</li>
{passwordRequirementItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<p className={styles.password_characters}>Allowed: <span>{passwordPolicy.allowed_special_characters_text}</span></p>
</Grid>
<Grid item container alignItems="center" justifyContent="center">
<Grid container item justify='center'>
Expand All @@ -195,6 +244,11 @@ const ResetPasswordPage = ({
disableElevation
fullWidth
type="submit"
disabled={
!!formik.errors.password ||
!formik.values.password_confirmation ||
formik.values.password_confirmation !== formik.values.password
}
>
{submitButtonText}
</Button>
Expand All @@ -207,6 +261,7 @@ const ResetPasswordPage = ({
<input type="hidden" value={initialValues.email} id="email" name="email"/>
<input type="hidden" name="token" value={token}/>
</form>
<p className={styles.help_link}>Need help? <a href="mailto:[email protected]">[email protected]</a></p>
</Container>
);
};
Expand Down
53 changes: 51 additions & 2 deletions resources/js/reset_password/reset_password.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
color: $text-color-dark;
background-color: white;

.email_field {
margin-bottom: 12px;
}

.turnstile {
margin-top: 10px;
margin-bottom: 10px;
Expand All @@ -36,15 +40,60 @@
}

.password_hint {
display: flex;
color: $hint-text-color;
vertical-align: middle;
background-color: #fafafa;
margin: 8px 8px;
}

.password_hint p:first-of-type {
margin: 0 0 8px;
}

.password_hint ul {
list-style: none;
margin-top: 8px;
padding-left: 10px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px 12px;
}

.password_hint ul li {
display: flex;
align-items: center;
gap: 60px;
}

.password_characters {
font-size: 0.85em;
}

.password_characters span {
letter-spacing: 0.5em;
}

.error_label {
color: #f44336;
font-size: 0.75rem;
margin: 4px 0 4px 14px;
}

}

.sr_only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}

.help_link {
margin: 1.5rem 0 0;
text-align: center;
}
}
Loading
Loading