All features

Feature breakdown · built with CraftHQ

SecurityCustomer portal

Customer sign-in with a one-time code (2FA)

Whole-team time at this build's pace

Estimated ~15 h

2 people incl. the product owner, requirements to merge

Estimate at this build's pace, not a measurement of this feature

External customers sign in with a password and a one-time code sent by email, and can recover access on their own.

Size: 19 FP · In-house IFPUG-style count, cross-checked by a second counter; not certified. Range 15 to 24. · How we counted 19 FP

How long a feature this size usually takes

Whole-team time — this build vs a typical team

19 FP · FP × 6.8 to 9.4 h (ISBSG 2023)

Whole-team time at this build's pace2 people incl. the product owner, requirements to merge
~15 h12 to 19 h

Estimate at this build's pace, not a measurement of this feature

A typical team, whole lifecycleRequirements to release · middle half of 512 comparable projects (Java, Agile), ISBSG 2023
~129 to 179 habout 3–4.5 person-weeksmedian ~150 h
grey band = a typical team, middle halfhatched = our whole-team estimate

Both figures are whole-team time over the whole lifecycle, so they compare like for like. Ours is this build's pace — about 0.8 hours per function point across the whole build (two people, including the product owner, with requirements and review time). The industry range is an ISBSG project-level figure applied to one feature, so it is indicative.

Workflow

How it moved through the workflow

Select a stage. The build shipped, so every stage is complete; a person approved the merge.

Request

External customers need two-step sign-in with a way to recover access on their own, so staff are not resetting passwords by hand.

Questions

One decision was recorded before building: how the one-time code should be entered.

Approved

The product owner signs off the breakdown by moving the story into the approved state, and the build runs against that reference.

Definition of ready

The questions are answered.
The acceptance criteria are testable.
The size is estimated.
The scope is checked for splitting.
The product owner has signed off.

Building

CraftHQ's build agents write the code and the tests together, run as a loop; a failing check halts the work until it passes.

Checks

Three checks run on every pull request: one on the product owner's machine as CraftHQ builds, one on the engineer's machine before review, and a code-quality scan in CI. Merging is gated by the CI check passing and a person's approval, enforced by branch protection.

Review

An engineer reviews the pull request and merges it.

Merged

The pull request is merged to main after a person approves it. Nothing merges without that approval.

Definition of done

Every acceptance criterion is checked; where a scenario has a dedicated test it is cited, and where it was verified by review that is stated.
The three checks are green.
A person approved the merge.

Breakdown

The feature breakdown

Business first, then the engineering view. Tab through it.

Overview

The gap it closes

External customers need a second factor and a way to recover access on their own, without staff resetting passwords by hand.

Who uses it

Users from a customer organisation signing in to their own workspace. Staff sign in separately, which is out of scope here.

User story

As a customer user, I want to sign in with a password and a one-time code so that a leaked or reused password alone does not let someone in.

Examples

Example 1

A customer enters their email and password, then the current one-time code from their inbox, and lands in their organisation's workspace.

Example 2

Someone tries codes at random. After three wrong codes the code is locked for fifteen minutes and must be re-issued, and asking for a fresh code is held to one every two minutes and three in fifteen, so a six-digit code cannot be ground down by guessing.

Dependencies & risks

Depends on

A way to deliver invites, reset links and one-time codes by email, and a server-side session store that can be revoked.

Risk & mitigation

A shared or guessed password alone would be weak. The one-time code adds a second factor; codes expire, are single-use and are rate-limited; and sessions can be revoked on the server.

How it works

Simplified diagrams of the parts involved, the steps of one request, and where it runs.

Architecture

People
Customersigns in
Client (untrusted)
Sign-in screenpassword then code
Your app
One-time-code gateissues and checks the code
Customer accountsper organisation
Sessionsrevocable on the server
Audit logsign-in and reset events
External services
Delivery serviceinvites, resets, codes
  • 1email and password
  • 2second factor
  • 3resolve the account and verify the password
  • 4issue a code by email
  • 5start session on a valid code
  • 6record outcome
Simplified.

Sequence

Browserthe customer
App serversign-in and code gate
Accounts + sessionsread and write
Delivery serviceone-time code
  • 1email and password
  • 2resolve the account and verify the password
  • 3send a one-time code by email
  • 4enter the code
  • 5check the code (unused, unexpired, within attempt limit)
  • 6start a session
  • 7signed in to the organisation
Simplified.

Infrastructure

Client (untrusted)
Web appthe customer sign-in, in the browser
Your app
App serversign-in and code gate; authenticates and pins the tenant
Databaseper-tenant isolation, sessions
External services
Delivery serviceinvites, resets, codes
  • 1sign-in request (authenticate + tenant check at the edge)
  • 2read account, write session
  • 3send code or reset link
Simplified.

Acceptance criteria

Each scenario is written as a Gherkin spec. The Engineering tab shows the test that covers each one, or notes where it was verified by review.

@sign-in @security
Feature: Customer sign-in with a one-time code
As a customer user
I want to sign in with a password and a one-time code sent by email
So that a leaked or reused password alone does not let someone in
External customers sign in with a password and a one-time code by email, and can recover access on their own.
Background:
Given a customer belongs to one organisation
And the customer has been invited to that organisation
Rule: Sign-in needs a password and a one-time code
Scenario: A customer signs in with both factors
Given a customer enters their email and correct password
When they enter the current one-time code from their inbox
Then they are signed in to their organisation
Scenario: A wrong one-time code is refused
Given a customer enters their email and correct password
When they enter an incorrect one-time code
Then they are not signed in
Scenario: Too many wrong codes lock sign-in
Given a customer has entered a correct password
When they submit the wrong one-time code more than the allowed number of times
Then sign-in is locked for fifteen minutes
Scenario: A used code cannot be replayed
Given a customer has already signed in with a one-time code
When the same code is submitted again
Then it is refused
Scenario: A resend within the cooldown is refused
Given a customer is at the one-time-code step
When they ask to resend the code within the cooldown
Then the resend is refused until the cooldown passes
Scenario: A fresh code replaces the old one
Given a customer asks to resend the code after the cooldown
When a new code is issued
Then the earlier code no longer works
Rule: A customer can recover access without help
Scenario: Forgotten password reset does not reveal whether an email exists
Given a person asks to reset a password for any email address
When they submit the request
Then the response is the same whether or not the email is registered
Scenario: A reset link is single-use and expiring
Given a customer has a password-reset link
When they complete the reset
Then they can set a new password and sign in
And the link cannot be used again
And their existing sessions are revoked
Rule: A session can be ended on the server
Scenario: A session is ended centrally
Given a customer is signed in
When the server revokes the session, for example on a password reset
Then the customer's next action requires signing in again
@edge-cases
Scenario Outline: One-time-code outcomes
Given a customer has entered a correct password
When they submit a "<code>" one-time code
Then the result is "<result>"
Examples:
| code | result |
| current | signed in |
| expired | refused |
| already used | refused |
| incorrect | refused |

Questions

Questions raised in planning, each with the decision taken.

How should the one-time code be entered?

A clear code-entry step reduces mistakes and handles paste, backspace and arrow keys the way people expect.

AA dedicated one-time-code entry that supports backspace, arrow keys and paste. · recommended
BA single plain text field for the code.
Decided: Build the dedicated code-entry step (option A).

How we sized it

A function point measures what the software does — its inputs, outputs, lookups and the data it stores — regardless of language or how many lines it took. Each counted item is weighted by complexity: a user input counts 3–6 points, an output or report 4–7, a lookup 3–6, data the feature owns 7–10, and data read from elsewhere 5–7.

  • Sign in with email and password
    User input
    4
  • Verify the one-time code
    User input
    4
  • Resend a one-time code
    User input
    3
  • Request a password reset
    User input
    4
  • Complete a password reset
    User input
    4
  • Total
    19
19 function points (range 15–24). IFPUG-style count: each user action, lookup, output and stored data set, weighted by complexity. Technical plumbing is excluded, and a data store is counted once in the feature that owns it.

Not counted here

  • Codes, sessions and reset-link plumbing: technical, not a user function
  • The account store: counted once, in its owning feature

Components touched

Sign-in step

Takes the email and password for a customer within one organisation; passwords are stored only as salted hashes, never in clear text.

One-time-code gate

After the account is resolved and the password verified, it issues a six-digit code by email, checks it, and lets the customer request a fresh one. Codes are stored only as a hash, expire after five minutes, are single-use, are invalidated when a new one is issued, and lock after three wrong attempts for fifteen minutes; resend is limited to a 120-second cooldown and three in fifteen minutes.

Forgot and reset

A self-service path. The reset link is single-use and expires; the request step does not reveal whether an email is registered; a completed reset revokes existing sessions.

Session store

Server-side sessions with a resolver and a session revocation, so a session can be ended centrally and all sessions dropped on a password reset.

Invite and admin reset

An administrator can invite a customer and reset their access.

Data touched

Customer accountsread

Resolved per organisation at sign-in, keyed by the login email; passwords held as salted hashes.

One-time codeswrite

A six-digit, single-use code per sign-in attempt, stored as a hash, with a five-minute expiry, an attempt counter and a resend cooldown.

Sessionswrite

Created on a successful two-step sign-in, revocable on the server, and dropped on a password reset.

Delivery serviceexternal

Sends invites, reset links and one-time codes by email.

Security decisions

  • Sign-in requires a password and a one-time code; passwords are stored as salted hashes only.
  • The six-digit code is delivered by email, stored only as a hash, expires after five minutes, is single-use, is invalidated when a new code is issued, and locks after three wrong attempts for fifteen minutes; resend is held to a 120-second cooldown and three in fifteen minutes.
  • Forgot-password does not reveal whether an email is registered; the reset link is single-use and expires; a completed reset revokes all existing sessions.
  • Sessions are server-side and can be revoked, so access can be ended centrally.

Subtasks

  • Per-organisation email and password sign-in with hashed passwords.
  • A one-time-code gate with expiry, single-use, attempt lockout and a resend cooldown.
  • A forgot-and-reset path with a single-use expiring link and session revocation.
  • Server-side sessions with session revocation.

How each scenario is verified

  • A customer signs in with both factors
    An integration test drives the sign-in flow from password to code to a started session.
  • A forgotten password reset
    A test over the reset path checks the link is single-use and that a reset revokes sessions.
  • A wrong or expired one-time code is refused
    Checked within the sign-in flow integration test.
  • A session is revoked
    Verified by review. Revocation uses the session store's version mechanism.

Risk tier

Core A person approves every merge. This is a Core-tier change, so it also carries the fullest set of automated tests; a regression here affects the whole product, not one page.

Start here

Want this on your backlog?

Get a free baseline report on one repository, or scope a 3-month pilot measured against what your team’s work costs today.

Measured from your own git history
Every pull request tested before review
A person approves every merge