Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/careers/careers.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/careers/careers.go |
| Original SHA-256 | 3ee4311591b2b1d0901c8314cd5178eb82e8daa8f222ec1aa958f8d03fcd639f |
| Git blob SHA | e628ae9393faff34e202ad173375ad9129873cb2 |
| Reading-copy SHA-256 | 3ee4311591b2b1d0901c8314cd5178eb82e8daa8f222ec1aa958f8d03fcd639f |
Recovered source transcript
Download complete source text ↓386 lines · 15.2 KB
// Package careers is the INBOUND path: a public job posting and a way for a person to apply to it.
//
// Every candidate in this product used to have to be imported or typed in by a recruiter, which meant it
// could only ever hold people somebody had already found. This is the difference between a database and a
// pipeline.
//
// THE PUBLIC URL CARRIES THE EMPLOYER'S SLUG, deliberately, and that is the design decision everything else
// follows from. A public request has no session, so it has no organization, so row level security correctly
// denies it everything. Something has to name the tenant before a query can run. A bare token would require a
// global token to tenant map that works across sharded databases; the slug is already in the directory, it is
// already how a tenant is identified, and it is information the applicant should see anyway, because they are
// applying to Acme and not to an opaque string.
//
// A SUBMISSION IS NOT A CANDIDATE. Under the OFCCP Internet Applicant rule an applicant is somebody who
// expressed interest AND whom the contractor then CONSIDERED for a particular position, and a federal
// contractor has to be able to show both events. So a submission lands in job_applications as what the person
// actually sent, and converting it into a candidate is a separate, recorded act by a named human. It also
// means spam never reaches the bench.
package careers
import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/missionworkforce/missionmatch/internal/platform/audit"
"github.com/missionworkforce/missionmatch/internal/platform/orgsettings"
"github.com/missionworkforce/missionmatch/internal/platform/storage"
"github.com/missionworkforce/missionmatch/internal/platform/tenant"
)
var (
ErrNoDatabase = errors.New("careers: no database configured")
ErrNotFound = errors.New("careers: no such posting")
ErrAlreadyApplied = errors.New("careers: an application from that address is already on file for this posting")
ErrInvalid = errors.New("careers: the submission is incomplete")
ErrPostingClosed = errors.New("careers: that posting is no longer accepting applications")
)
// Service serves both the public surface and the tenant side that manages it.
//
// control is the CONTROL PLANE pool, used for exactly one thing: turning a public slug into an organization
// id. It is the directory, it is the only place that mapping authoritatively lives, and it is the only query
// in this package that runs outside a tenant transaction.
type Service struct {
db *tenant.DB
control *pgxpool.Pool
store storage.Storer
aud *audit.Writer
// settings resolves the organization's own mail configuration so a booked interview can send a calendar
// invitation. Nil means a booking still succeeds and simply sends nothing, because a missing invitation
// is an inconvenience and a lost booking is not.
settings *orgsettings.Store
}
func NewService(db *tenant.DB, control *pgxpool.Pool, store storage.Storer, aud *audit.Writer) *Service {
return &Service{db: db, control: control, store: store, aud: aud}
}
// WithSettings injects the organization settings store used for calendar invitations.
func (s *Service) WithSettings(store *orgsettings.Store) *Service {
s.settings = store
return s
}
// Posting is the PUBLIC view. Every field on it was written for publication. Nothing here is derived from
// the requisition, so the agency's bill rate, its margin and its customer's name cannot leak by accident.
type Posting struct {
Token string `json:"token"`
Title string `json:"title"`
Summary string `json:"summary,omitempty"`
Responsibilities string `json:"responsibilities,omitempty"`
Qualifications string `json:"qualifications,omitempty"`
Location string `json:"location,omitempty"`
RemoteAllowed bool `json:"remote_allowed"`
EmploymentType string `json:"employment_type,omitempty"`
Clearance string `json:"clearance,omitempty"`
PayRange string `json:"pay_range,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
ClosesAt *time.Time `json:"closes_at,omitempty"`
Employer string `json:"employer"`
}
// resolveOrg turns a public slug into an organization id, using the control plane directory. It is the one
// place a caller supplied string names a tenant, so it is deliberately the narrowest query in the package: an
// exact, case insensitive slug match on an ACTIVE organization, returning an identifier and nothing else.
func (s *Service) resolveOrg(ctx context.Context, slug string) (uuid.UUID, error) {
if s.control == nil {
return uuid.Nil, ErrNoDatabase
}
slug = strings.ToLower(strings.TrimSpace(slug))
if slug == "" || len(slug) > 63 {
return uuid.Nil, ErrNotFound
}
ctx, cancel := context.WithTimeout(ctx, 8*time.Second)
defer cancel()
// THROUGH A SECURITY DEFINER FUNCTION, not a plain query. organizations carries FORCE ROW LEVEL SECURITY
// and the application role is NOBYPASSRLS, so a direct SELECT here returns zero rows: a public request has
// no app.current_org_id to satisfy the policy with. That is the tenancy contract working, not a bug to
// route around, and it made the careers page report a live posting as unavailable.
var id uuid.UUID
err := s.control.QueryRow(ctx, `SELECT public_org_id_by_slug($1)`, slug).Scan(&id)
if errors.Is(err, pgx.ErrNoRows) {
// A slug that does not exist and one that is not active are the SAME answer, so the public surface
// cannot be used to enumerate which customers exist or which have been suspended.
return uuid.Nil, ErrNotFound
}
if err != nil {
// A NULL result scans into uuid.UUID as an error rather than as ErrNoRows, because the function
// returns one row containing NULL when the slug is unknown. Both mean the same thing to a caller.
if strings.Contains(err.Error(), "NULL") || strings.Contains(err.Error(), "cannot scan") {
return uuid.Nil, ErrNotFound
}
return uuid.Nil, err
}
if id == uuid.Nil {
return uuid.Nil, ErrNotFound
}
return id, nil
}
// ListPublicPostings serves a careers page.
func (s *Service) ListPublicPostings(ctx context.Context, slug string) ([]Posting, string, error) {
if s.db == nil {
return nil, "", ErrNoDatabase
}
org, err := s.resolveOrg(ctx, slug)
if err != nil {
return nil, "", err
}
out := make([]Posting, 0, 32)
employer := ""
err = s.db.RunInTenantTx(tenant.WithOrg(ctx, org.String()), func(ctx context.Context, tx pgx.Tx) error {
// The published only rule lives in the SQL function, in one place, so a future direct query cannot
// forget it and put a draft on the open internet.
rows, e := tx.Query(ctx, `SELECT public_token, title, summary, location_text, remote_allowed,
employment_type, clearance_text, published_at, organization_name
FROM public_postings_by_slug($1)`, slug)
if e != nil {
return e
}
defer rows.Close()
for rows.Next() {
var p Posting
if e := rows.Scan(&p.Token, &p.Title, &p.Summary, &p.Location, &p.RemoteAllowed,
&p.EmploymentType, &p.Clearance, &p.PublishedAt, &employer); e != nil {
return e
}
p.Employer = employer
out = append(out, p)
}
if e := rows.Err(); e != nil {
return e
}
// THE EMPLOYER NAME CANNOT COME FROM THE POSTINGS ALONE. It was read out of each row, so an employer
// with nothing open right now rendered a careers page with no name on it: a visitor following a link
// from a job board landed on an anonymous page saying nothing is available, which reads as a broken
// link rather than as a real employer between vacancies.
//
// Read separately when the list is empty rather than always, so the common path still costs one query.
// The organization is already resolved and the tenant context is already set, so this discloses
// nothing the slug did not already establish.
if len(out) == 0 {
var name, display *string
if e := tx.QueryRow(ctx, `SELECT name, display_name FROM organizations`).Scan(&name, &display); e != nil {
// A name that cannot be read is not worth failing the page over. The visitor sees the generic
// heading, which is what they saw before this existed.
return nil
}
if display != nil && *display != "" {
employer = *display
} else if name != nil {
employer = *name
}
}
return nil
})
return out, employer, err
}
// GetPublicPosting serves one advertisement.
func (s *Service) GetPublicPosting(ctx context.Context, slug, token string) (Posting, error) {
var p Posting
if s.db == nil {
return p, ErrNoDatabase
}
if !validToken(token) {
return p, ErrNotFound
}
org, err := s.resolveOrg(ctx, slug)
if err != nil {
return p, err
}
err = s.db.RunInTenantTx(tenant.WithOrg(ctx, org.String()), func(ctx context.Context, tx pgx.Tx) error {
e := tx.QueryRow(ctx, `SELECT title, summary, responsibilities, qualifications, location_text,
remote_allowed, employment_type, clearance_text, pay_range_text,
published_at, closes_at, organization_name
FROM public_posting_by_token($1)`, token).
Scan(&p.Title, &p.Summary, &p.Responsibilities, &p.Qualifications, &p.Location,
&p.RemoteAllowed, &p.EmploymentType, &p.Clearance, &p.PayRange,
&p.PublishedAt, &p.ClosesAt, &p.Employer)
if errors.Is(e, pgx.ErrNoRows) {
return ErrNotFound
}
return e
})
p.Token = token
return p, err
}
// Submission is what a person sends.
type Submission struct {
FullName string `json:"full_name"`
Email string `json:"email"`
Phone string `json:"phone"`
Location string `json:"location"`
CoverNote string `json:"cover_note"`
Clearance string `json:"clearance"`
Source string `json:"source"`
ConsentText string `json:"consent_text"`
Consented bool `json:"consented"`
ResumeName string `json:"-"`
ResumeType string `json:"-"`
ResumeBytes []byte `json:"-"`
SubmittedIP string `json:"-"`
}
// Apply records a submission.
//
// IT NEVER SAYS WHETHER THE PERSON IS ALREADY KNOWN. A duplicate to the same posting is refused with a
// deliberately unrevealing message, and nothing about an existing candidate is ever confirmed, because a
// public endpoint that answers "we already have you" is a public endpoint that tells anybody who is on an
// agency's bench.
func (s *Service) Apply(ctx context.Context, slug, token string, in Submission) (string, error) {
if s.db == nil {
return "", ErrNoDatabase
}
if !validToken(token) {
return "", ErrNotFound
}
in.FullName = strings.TrimSpace(in.FullName)
in.Email = strings.ToLower(strings.TrimSpace(in.Email))
if in.FullName == "" || len(in.FullName) > 200 {
return "", fmt.Errorf("%w: a name is required", ErrInvalid)
}
if !plausibleEmail(in.Email) {
return "", fmt.Errorf("%w: a valid email address is required", ErrInvalid)
}
if !in.Consented {
// CONSENT IS NOT OPTIONAL AND IT IS NOT ASSUMED. A submission without it is refused rather than
// stored with an empty consent column, because a record whose legal basis is blank is a record that
// cannot be defended.
return "", fmt.Errorf("%w: consent is required to submit an application", ErrInvalid)
}
in.CoverNote = clip(in.CoverNote, 5000)
in.Phone = clip(strings.TrimSpace(in.Phone), 60)
in.Location = clip(strings.TrimSpace(in.Location), 200)
in.Clearance = clip(strings.TrimSpace(in.Clearance), 120)
in.Source = clip(strings.TrimSpace(in.Source), 120)
if in.Source == "" {
in.Source = "careers_page"
}
in.ConsentText = clip(in.ConsentText, 2000)
org, err := s.resolveOrg(ctx, slug)
if err != nil {
return "", err
}
var key, sum string
if len(in.ResumeBytes) > 0 {
if s.store == nil {
// Refused rather than silently dropping the resume. An applicant who attached one and had it
// discarded has been told their application went through when half of it did not.
return "", errors.New("careers: this node cannot accept file attachments")
}
sum = storage.SHA256Hex(in.ResumeBytes)
key = fmt.Sprintf("org/%s/inbound/%s/%s", org, token, sum)
if e := s.store.Put(ctx, key, in.ResumeBytes); e != nil {
return "", fmt.Errorf("careers: store resume: %w", e)
}
}
var id string
err = s.db.RunInTenantTx(tenant.WithOrg(ctx, org.String()), func(ctx context.Context, tx pgx.Tx) error {
// The posting must be PUBLISHED and open, asked through the same function the public read uses, so
// an application cannot be filed against a draft or a closed role by replaying an old link.
var postingID uuid.UUID
e := tx.QueryRow(ctx, `SELECT posting_id FROM public_posting_by_token($1)`, token).Scan(&postingID)
if errors.Is(e, pgx.ErrNoRows) {
return ErrPostingClosed
}
if e != nil {
return e
}
e = tx.QueryRow(ctx, `
INSERT INTO job_applications
(organization_id, posting_id, full_name, email, phone, location_text, cover_note,
clearance_self_reported, resume_storage_key, resume_file_name, resume_media_type,
resume_sha256, source, consent_text, consented_at, submitted_ip)
VALUES (NULLIF(current_setting('app.current_org_id', true), '')::uuid,
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13, now(), $14)
RETURNING id::text`,
postingID, in.FullName, in.Email, in.Phone, in.Location, in.CoverNote, in.Clearance,
key, clip(in.ResumeName, 180), in.ResumeType, sum, in.Source, in.ConsentText,
nullIfEmpty(in.SubmittedIP)).Scan(&id)
if e != nil {
if strings.Contains(e.Error(), "duplicate key") || strings.Contains(e.Error(), "unique constraint") {
return ErrAlreadyApplied
}
return e
}
if s.aud != nil {
// Audited WITHOUT the cover note and without the resume. What is recorded is that an application
// arrived for a posting from an address, which is the applicant flow log entry; the content of
// what they wrote lives on the row and does not need a second copy in an append only table.
body, _ := json.Marshal(map[string]any{
"posting_id": postingID.String(), "source": in.Source, "has_resume": key != "",
})
return s.aud.OnTx(ctx, tx, audit.Event{
Action: "careers.application.received", EntityType: "job_application", EntityID: id,
NewValues: body, IPAddress: in.SubmittedIP,
})
}
return nil
})
return id, err
}
func validToken(t string) bool {
if len(t) < 16 || len(t) > 64 {
return false
}
for _, r := range t {
if (r < 'a' || r > 'z') && (r < '0' || r > '9') {
return false
}
}
return true
}
// NewPostingToken mints the public URL key. It is random rather than derived, so a token cannot be guessed
// from a requisition identifier and postings cannot be walked.
func NewPostingToken() (string, error) {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
func plausibleEmail(v string) bool {
if len(v) < 6 || len(v) > 320 || strings.ContainsAny(v, " \t\r\n<>,;") {
return false
}
at := strings.LastIndexByte(v, '@')
if at <= 0 || at == len(v)-1 {
return false
}
dot := strings.IndexByte(v[at+1:], '.')
return dot > 0 && dot < len(v[at+1:])-1
}
func clip(s string, n int) string {
if len(s) > n {
return s[:n]
}
return s
}
func nullIfEmpty(s string) any {
if strings.TrimSpace(s) == "" {
return nil
}
return s
}