mirror of
https://github.com/penpot/penpot.git
synced 2026-07-27 00:18:17 +00:00
🐛 Fix email validation to reject consecutive dots in domain (#10096)
* 🐛 Tighten email validation regex to reject consecutive dots in domain * 📎 Add minor adjustments to gh-issue-from-pr skill
This commit is contained in:
parent
3419f7e60a
commit
d1fb1d34a1
@ -39,8 +39,8 @@ Identify:
|
||||
|
||||
| Field | Source | Rule |
|
||||
|-------|--------|------|
|
||||
| **Title** | PR title | Rewrite from user perspective. Strip leading emoji prefixes (`:bug:`, `:sparkles:`, `:tada:`). Focus on observable behavior. Use imperative mood. |
|
||||
| **Labels** | PR labels | Copy user-facing labels (`bug`, `enhancement`, `community contribution`). Skip workflow labels (`backport candidate`, `team-qa`). |
|
||||
| **Title** | PR title | Rewrite from user perspective. Strip leading emoji prefixes (`:bug:`, `:sparkles:`, `:tada:`). Focus on observable behavior. Use imperative mood. Use the `issue-title` skill to generate this. |
|
||||
| **Labels** | PR labels | Copy `community contribution` if present. Skip `bug` and `enhancement` (redundant with Issue Type). Skip workflow labels (`backport candidate`, `team-qa`). |
|
||||
| **Milestone** | PR milestone | **Always copy what's on the PR.** Fetch with: `gh pr view <PR_NUMBER> --json milestone --jq '.milestone.title'` If the PR has no milestone, create the issue without one. |
|
||||
| **Project** | Always `Main` | Penpot uses the `Main` project (number 8) for all issues. |
|
||||
| **Body** | PR's user-facing section | Extract steps to reproduce or feature description. Omit internal details. Use templates below. |
|
||||
@ -101,8 +101,7 @@ Create:
|
||||
gh issue create \
|
||||
--repo penpot/penpot \
|
||||
--title "<Title>" \
|
||||
--label "<label1>" \
|
||||
--label "<label2>" \
|
||||
--label "community contribution" \ # only if PR has this label
|
||||
--milestone "<milestone>" \
|
||||
--project "Main" \
|
||||
--body-file /tmp/issue-body.md
|
||||
@ -198,12 +197,10 @@ rm -f /tmp/issue-body.md /tmp/pr-body.md
|
||||
|
||||
| PR has | Issue gets |
|
||||
|--------|-----------|
|
||||
| `bug` | `bug` |
|
||||
| `enhancement` | `enhancement` |
|
||||
| `community contribution` | `community contribution` |
|
||||
| `bug`, `enhancement` | *(skip — redundant with Issue Type)* |
|
||||
| `backport candidate` | *(skip — workflow label)* |
|
||||
| `team-qa` | *(skip — workflow label)* |
|
||||
| No user-facing label | Infer from title: `:bug:` → `bug`, `:sparkles:` → `enhancement` |
|
||||
|
||||
## Issue Type mapping
|
||||
|
||||
|
||||
@ -448,18 +448,22 @@
|
||||
::oapi/type "string"
|
||||
::oapi/format "uuid"}})
|
||||
|
||||
(def email-re #"[a-zA-Z0-9_.+-\\\\]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+")
|
||||
;; Strict email regex aligned with app.common.spec/email-re.
|
||||
;; Local part: valid RFC chars, no leading/trailing dot, no consecutive dots.
|
||||
;; Domain: labels can't start/end with hyphen, no empty labels.
|
||||
;; TLD: at least 2 alphabetic chars.
|
||||
(def email-re
|
||||
#"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,63}")
|
||||
|
||||
(defn parse-email
|
||||
[s]
|
||||
(if (string? s)
|
||||
(first (re-seq email-re s))
|
||||
nil))
|
||||
(when (and (string? s) (re-matches email-re s))
|
||||
s))
|
||||
|
||||
(defn email-string?
|
||||
[s]
|
||||
(and (string? s)
|
||||
(re-seq email-re s)))
|
||||
(some? (re-matches email-re s))))
|
||||
|
||||
(register!
|
||||
{:type ::email
|
||||
|
||||
@ -188,3 +188,60 @@
|
||||
(t/is (= false (decode-s "f")))
|
||||
(t/is (= true (decode-s "1")))
|
||||
(t/is (= false (decode-s "0")))))
|
||||
|
||||
(t/deftest test-email-validation
|
||||
(t/testing "accepts well-formed email addresses"
|
||||
(doseq [email ["user@domain.com"
|
||||
"user.name@domain.com"
|
||||
"user+tag@domain.com"
|
||||
"user-name@domain.com"
|
||||
"user_name@domain.com"
|
||||
"user123@domain.com"
|
||||
"USER@DOMAIN.COM"
|
||||
"u@domain.io"
|
||||
"user@sub.domain.com"
|
||||
"user@domain.co.uk"
|
||||
"user@domain.dev"
|
||||
"a@bc.co"]]
|
||||
(t/is (sm/validate ::sm/email email) (str "should accept: " email))
|
||||
(t/is (= email (sm/decode ::sm/email email sm/json-transformer)))))
|
||||
|
||||
(t/testing "rejects domain with consecutive dots (dot-dot)"
|
||||
(t/is (false? (sm/validate ::sm/email "user@gmail.com..")))
|
||||
(t/is (false? (sm/validate ::sm/email "user@sub..domain.com")))
|
||||
(t/is (false? (sm/validate ::sm/email "eissaalbothigi@gmail.com..")))
|
||||
(t/is (nil? (sm/parse-email "user@gmail.com..")))
|
||||
(t/is (nil? (sm/parse-email "eissaalbothigi@gmail.com.."))))
|
||||
|
||||
(t/testing "rejects domain ending with a dot"
|
||||
(t/is (false? (sm/validate ::sm/email "user@domain.")))
|
||||
(t/is (nil? (sm/parse-email "user@domain."))))
|
||||
|
||||
(t/testing "rejects domain starting with a dot"
|
||||
(t/is (false? (sm/validate ::sm/email "user@.domain.com")))
|
||||
(t/is (nil? (sm/parse-email "user@.domain.com"))))
|
||||
|
||||
(t/testing "rejects local part with consecutive dots"
|
||||
(t/is (false? (sm/validate ::sm/email "user..name@domain.com")))
|
||||
(t/is (nil? (sm/parse-email "user..name@domain.com"))))
|
||||
|
||||
(t/testing "rejects local part starting with a dot"
|
||||
(t/is (false? (sm/validate ::sm/email ".user@domain.com")))
|
||||
(t/is (nil? (sm/parse-email ".user@domain.com"))))
|
||||
|
||||
(t/testing "rejects label starting or ending with hyphen"
|
||||
(t/is (false? (sm/validate ::sm/email "user@-domain.com")))
|
||||
(t/is (false? (sm/validate ::sm/email "user@domain-.com"))))
|
||||
|
||||
(t/testing "rejects TLD shorter than 2 chars"
|
||||
(t/is (false? (sm/validate ::sm/email "user@domain.c"))))
|
||||
|
||||
(t/testing "rejects domain without a dot"
|
||||
(t/is (false? (sm/validate ::sm/email "user@domain"))))
|
||||
|
||||
(t/testing "rejects empty or malformed emails"
|
||||
(t/is (false? (sm/validate ::sm/email "")))
|
||||
(t/is (false? (sm/validate ::sm/email "@domain.com")))
|
||||
(t/is (false? (sm/validate ::sm/email "user@")))
|
||||
(t/is (false? (sm/validate ::sm/email "userdomain.com")))
|
||||
(t/is (false? (sm/validate ::sm/email "user@@domain.com")))))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user