Adds the full Next.js app (app/, components/, lib/, scripts/, assets/, docs/, Dockerfile, compose, configs) to version control. Security: - CIPC BizPortal customer-code logins (Login 1 + Login 3) now read CIPC_LOGIN1_CODE / CIPC_LOGIN1_PASSWORD / CIPC_LOGIN3_CODE / CIPC_LOGIN3_PASSWORD from .env.local instead of source. - BizPortal personal login in apply-bizportal route reads BIZPORTAL_ID_NO / BIZPORTAL_PASSWORD. - Added .env.example as the committed template. .gitignore excludes .env*, node_modules, .next, logs, sqlite, Playwright storage_state.json (live CIPC cookies), scratch dirs, client PDFs and debug screenshots.
86 lines
No EOL
3.1 KiB
Python
86 lines
No EOL
3.1 KiB
Python
"""Decisive test: does a file contain embedded signature IMAGES?
|
|
|
|
Text extraction can't tell a signed scan from a blank template — both have the
|
|
same text layer. Embedded image XObjects can: a scanned signature leaves an
|
|
image where the template has bare underscores.
|
|
"""
|
|
import io
|
|
import urllib.parse
|
|
import urllib.request
|
|
from pathlib import Path
|
|
from pypdf import PdfReader
|
|
|
|
JEI = Path(r"C:\Users\SJ\.minimax-agent\projects\JEI")
|
|
env = {}
|
|
for line in (JEI / ".env.local").read_text(encoding="utf-8").splitlines():
|
|
line = line.strip()
|
|
if line and not line.startswith("#") and "=" in line:
|
|
k, v = line.split("=", 1)
|
|
env[k.strip()] = v.strip().strip('"').strip("'")
|
|
PB = env.get("NEXT_PUBLIC_PB_URL") or env["PB_URL"]
|
|
TOK = env["PB_TOKEN"]
|
|
|
|
FILES = [
|
|
("LOCAL UFC - Shareholders Resolution ... (attached)",
|
|
None,
|
|
r"C:\Users\SJ\.minimax\v2\assets\2026\10\12\13-41-56-012-asset_20261012-134156-012_a0299b3ae338_88b7c484-UFC - Shareholders Resolution Director Sune Resigned.pdf"),
|
|
("LOCAL UFC - Sune Resignation Letter (attached)",
|
|
None,
|
|
r"C:\Users\SJ\.minimax\v2\assets\2026\10\12\13-41-56-017-asset_20261012-134156-017_9aec1b4380ba_261a3e6b-UFC - Sune Resignation Letter.pdf"),
|
|
("STORED resolution.signed_letter", ("pbc_266170881", "e0dbxprazb70rqx", "corrected_v2_avwpnq7o93.pdf"), None),
|
|
("STORED resignation.letter", ("ysterbaard_director_resignations", "8cfie18uhgy0n0o",
|
|
"resignation_letter_the_ultimate_fuel_company_1jk8t34nf3.pdf"), None),
|
|
("STORED resignation.signature", ("ysterbaard_director_resignations", "8cfie18uhgy0n0o",
|
|
"tbde1evt2m6_8mz8kutpki.pdf"), None),
|
|
]
|
|
|
|
|
|
def count_images(reader) -> int:
|
|
n = 0
|
|
for page in reader.pages:
|
|
try:
|
|
n += len(page.images)
|
|
except Exception:
|
|
pass
|
|
return n
|
|
|
|
|
|
def has_annot_sig(reader) -> int:
|
|
n = 0
|
|
for page in reader.pages:
|
|
try:
|
|
for a in page.get("/Annots") or []:
|
|
obj = a.get_object()
|
|
if obj.get("/Subtype") == "/Widget" or obj.get("/FT") == "/Sig":
|
|
n += 1
|
|
except Exception:
|
|
pass
|
|
return n
|
|
|
|
|
|
for label, pb, path in FILES:
|
|
if pb:
|
|
col, rid, fname = pb
|
|
url = f"{PB}/api/files/{col}/{rid}/{urllib.parse.quote(fname)}"
|
|
req = urllib.request.Request(url, headers={"Authorization": TOK})
|
|
with urllib.request.urlopen(req, timeout=120) as r:
|
|
b = r.read()
|
|
else:
|
|
b = Path(path).read_bytes()
|
|
|
|
rd = PdfReader(io.BytesIO(b))
|
|
imgs = count_images(rd)
|
|
sigs = has_annot_sig(rd)
|
|
# Look for signature-ish words in the text layer
|
|
txt = " ".join((p.extract_text() or "") for p in rd.pages).lower()
|
|
markers = []
|
|
for m in ("approved by", "signed at", "signature", "signed"):
|
|
if m in txt:
|
|
markers.append(m)
|
|
print(f"{label}")
|
|
print(f" size={len(b):>8} B pages={len(rd.pages)} images={imgs} digital-sig-widgets={sigs}")
|
|
print(f" text markers present: {markers}")
|
|
print()
|
|
|
|
print("VERDICT: images==0 on every file => none of these contain a scanned")
|
|
print("signature; all are blank templates with signature lines only.") |