Skip to content

Commit c4e92e1

Browse files
committed
feat: add CIE OIDC authentication handlers and configuration
1 parent 8d2a522 commit c4e92e1

5 files changed

Lines changed: 151 additions & 19 deletions

File tree

public/styles/stwStyle.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/styles/style.css

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
--special-purple: color-mix(in srgb, #b39ddb 70%, white);
1515
--special-gray: color-mix(in srgb, #b0b0b0 60%, white);
1616

17-
--special: var(--special-blue);
17+
--special: var(--special-green);
1818
--special-hover: color-mix(in srgb, var(--special) 60%, white);
1919
--on-special: hsl(0, 0%, 98%);
2020
--error: #ff6f61;
@@ -23,6 +23,7 @@
2323
--padding-reduced: 0.25rem;
2424
--padding-compact: 0.125rem;
2525
--thickness: 0.25rem;
26+
--line-height: 1.5rem;
2627

2728
--min-menu-width: 12rem;
2829

@@ -45,9 +46,14 @@
4546
}
4647
}
4748

49+
html {
50+
background: var(--bg);
51+
color: var(--text);
52+
}
53+
4854
body {
4955
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
50-
font-size: 12pt;
56+
font-size: inherit;
5157
background: var(--bg);
5258
color: var(--text);
5359
}
@@ -63,7 +69,7 @@ dialog[open] {
6369
font-size: smaller;
6470
border: thin solid var(--border);
6571
z-index: 1000;
66-
max-width: 50rem;
72+
min-width: 50rem;
6773
}
6874

6975
dialog h1 {

stwComponents/stwSecurityCIE.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// file: cie_oidc_deno.ts
2+
3+
// Configurazione (usa Deno.env per produzione)
4+
const CLIENT_ID = Deno.env.get("CIE_CLIENT_ID") ?? "INSERISCI_CLIENT_ID";
5+
const CLIENT_SECRET = Deno.env.get("CIE_CLIENT_SECRET") ?? "INSERISCI_CLIENT_SECRET";
6+
const REDIRECT_URI = Deno.env.get("CIE_REDIRECT_URI") ?? "https://tuo-portale.it/callback";
7+
8+
// Endpoint OIDC CIE
9+
const CIE_AUTH_ENDPOINT =
10+
"https://idserver.servizicie.interno.gov.it/auth/realms/cie/protocol/openid-connect/auth";
11+
const CIE_TOKEN_ENDPOINT =
12+
"https://idserver.servizicie.interno.gov.it/auth/realms/cie/protocol/openid-connect/token";
13+
const CIE_USERINFO_ENDPOINT =
14+
"https://idserver.servizicie.interno.gov.it/auth/realms/cie/protocol/openid-connect/userinfo";
15+
16+
// Handler /login → redirect verso CIE
17+
function loginHandler(_req: Request): Response {
18+
const state = crypto.randomUUID();
19+
const nonce = crypto.randomUUID();
20+
21+
// In produzione salva state/nonce in sessione (cookie, redis, ecc.)
22+
const params = new URLSearchParams({
23+
client_id: CLIENT_ID,
24+
redirect_uri: REDIRECT_URI,
25+
response_type: "code",
26+
scope: "openid profile",
27+
state,
28+
nonce,
29+
});
30+
31+
const url = `${CIE_AUTH_ENDPOINT}?${params.toString()}`;
32+
return Response.redirect(url, 302);
33+
}
34+
35+
// Handler /callback → scambia code, chiama /userinfo, estrae CF
36+
async function callbackHandler(req: Request): Promise<Response> {
37+
const url = new URL(req.url);
38+
const code = url.searchParams.get("code");
39+
const error = url.searchParams.get("error");
40+
41+
if (error) {
42+
return new Response(`Errore da CIE: ${error}`, { status: 400 });
43+
}
44+
if (!code) {
45+
return new Response("Manca il parametro 'code'", { status: 400 });
46+
}
47+
48+
// Scambio code → token
49+
const tokenRes = await fetch(CIE_TOKEN_ENDPOINT, {
50+
method: "POST",
51+
headers: {
52+
"Content-Type": "application/x-www-form-urlencoded",
53+
},
54+
body: new URLSearchParams({
55+
grant_type: "authorization_code",
56+
code,
57+
redirect_uri: REDIRECT_URI,
58+
client_id: CLIENT_ID,
59+
client_secret: CLIENT_SECRET,
60+
}),
61+
});
62+
63+
if (!tokenRes.ok) {
64+
const text = await tokenRes.text();
65+
return new Response(`Errore token endpoint: ${tokenRes.status} - ${text}`, {
66+
status: 500,
67+
});
68+
}
69+
70+
const tokenData = await tokenRes.json();
71+
const accessToken = tokenData.access_token;
72+
if (!accessToken) {
73+
return new Response("Access token mancante nella risposta CIE", {
74+
status: 500,
75+
});
76+
}
77+
78+
// Chiamata allo UserInfo endpoint
79+
const userInfoRes = await fetch(CIE_USERINFO_ENDPOINT, {
80+
headers: {
81+
Authorization: `Bearer ${accessToken}`,
82+
},
83+
});
84+
85+
if (!userInfoRes.ok) {
86+
const text = await userInfoRes.text();
87+
return new Response(`Errore userinfo endpoint: ${userInfoRes.status} - ${text}`, {
88+
status: 500,
89+
});
90+
}
91+
92+
const user = await userInfoRes.json() as Record<string, unknown>;
93+
94+
// Estrazione codice fiscale
95+
const fiscalNumber = typeof user["fiscal_number"] === "string"
96+
? (user["fiscal_number"] as string)
97+
: undefined;
98+
99+
const codiceFiscale = fiscalNumber?.startsWith("TINIT-")
100+
? fiscalNumber.substring("TINIT-".length)
101+
: fiscalNumber;
102+
103+
const payload = {
104+
rawUserInfo: user,
105+
fiscal_number: fiscalNumber,
106+
codice_fiscale: codiceFiscale,
107+
};
108+
109+
return new Response(JSON.stringify(payload, null, 2), {
110+
headers: { "Content-Type": "application/json; charset=utf-8" },
111+
});
112+
}
113+
114+
// Server Deno minimale
115+
Deno.serve((req) => {
116+
const url = new URL(req.url);
117+
118+
if (url.pathname === "/login") {
119+
return loginHandler(req);
120+
}
121+
122+
if (url.pathname === "/callback") {
123+
return callbackHandler(req);
124+
}
125+
126+
return new Response(
127+
`CIE OIDC demo attiva.\n\n- /login → redirect a "Entra con CIE"\n- /callback → endpoint di ritorno`,
128+
{ headers: { "Content-Type": "text/plain; charset=utf-8" } },
129+
);
130+
});

stwStyles/stwTabs.css

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,36 @@
1111

1212
.stwHTabs>div {
1313
display: flex;
14-
border-bottom: thin solid var(--border);
1514
}
1615

1716
.stwVTabs>div {
1817
display: flex;
1918
flex-direction: column;
20-
border-right: thin solid var(--border);
2119
}
2220

23-
.stwHTabs dt[data-ref] {
21+
.stwTabs dt[data-ref] {
2422
display: inline-block;
25-
padding: var(--thickness) var(--padding);
23+
padding: var(--padding-reduced) calc(2 * var(--padding));
2624
cursor: pointer;
27-
border-top: thin solid var(--border);
28-
border-left: thin solid var(--border);
25+
background-color: var(--bg-faint);
26+
min-width: 8rem;
2927
}
30-
.stwHTabs dt[data-ref]:last-of-type {
31-
border-right: thin solid var(--border);
28+
29+
.stwTabs i.fa-xmark {
30+
float: inline-end;
31+
line-height: var(--line-height);
3232
}
3333

3434
.stwVTabs dd:first-of-type {
3535
flex-grow: 1;
3636
}
3737

38-
.stwVTabs dt[data-ref] {
39-
padding: var(--padding);
40-
cursor: pointer;
41-
}
42-
4338
.stwTabs dd {
4439
display: flex;
4540
margin: 0;
4641
}
4742

4843
.stwTabs dd>article {
49-
padding: 0;
44+
padding: var(--padding-reduced);
45+
border: thin solid var(--border);
5046
}

wiki

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Subproject commit 1b127f54c3d096265b7849a538c002a84db62413
1+
Subproject commit 05c0abad40230858f990e6e61933f3ed144ee238

0 commit comments

Comments
 (0)