Working auth system

main-old
Antonio De Lucreziis 2 years ago
parent b0f4eb069e
commit 6a692fdf5f

@ -10,3 +10,6 @@ CHAT_URL=https://chat.phc.dm.unipi.it
EMAIL=macchinisti@lists.dm.unipi.it EMAIL=macchinisti@lists.dm.unipi.it
USER_PAGES_BASE_URL=https://poisson.phc.dm.unipi.it/~ USER_PAGES_BASE_URL=https://poisson.phc.dm.unipi.it/~
# AuthService
AUTH_SERVICE_HOST=:memory:

3
.gitignore vendored

@ -13,3 +13,6 @@ node_modules/
# Don't version generated files # Don't version generated files
public/js/*.min.js public/js/*.min.js
# Executables
phc-website-server

@ -91,3 +91,9 @@ $ fd -e js | entr make js
Rappresentano link ad altri servizi forniti, è comodo impostarli per testare tutto in locale su varie porte (e poi in produzione i vari url diventano link a sotto-domini del sito principale). Rappresentano link ad altri servizi forniti, è comodo impostarli per testare tutto in locale su varie porte (e poi in produzione i vari url diventano link a sotto-domini del sito principale).
Per ora ci sono `GIT_URL`, `CHAT_URL` e `USER_PAGES_BASE_URL`. Per ora ci sono `GIT_URL`, `CHAT_URL` e `USER_PAGES_BASE_URL`.
## Altri Servizi
Questo servizio dipende dal servizio di autenticazione per permettere agli utenti di autenticarsi usando vari meccanismi.
Il servizio di autenticazione di default girerà su `localhost:3535` ed è documentato [sulla repo auth-service](https://git.phc.dm.unipi.it/phc/auth-service/)

@ -1,41 +0,0 @@
package main
// AuthService rappresenta un servizio di autenticazione
// type AuthService interface {
// GetUsers() []User
// GetUser(username string) User
// // LoginUser if successful returns the token for this user that will be stored in an HTTP cookie.
// LoginUser(username, password string) (string, error)
// }
// LdapService ...
type LdapService struct {
URL string
}
// FakeService ...
type FakeService struct {
URL string
}
// NewAuthenticationService crea un nuovo servizio di autenticazione e controlla se è attivo
// func NewAuthenticationService(url string) (*LdapService, error) {
// service := new(LdapService)
// service.URL = url
// res, err := service.Get("status")
// if err != nil {
// return nil, err
// }
// status, _ := ioutil.ReadAll(res.Body)
// if string(status) != "true" {
// log.Fatalf("Authentication service isn't online, status: '%s'", status)
// }
// return service, nil
// }

@ -0,0 +1,42 @@
package auth
type User interface {
GetUsername() string
GetName() string
GetSurname() string
GetFullName() string
}
type Session interface {
GetUsername() string
GetToken() string
}
type AuthenticatorService interface {
GetUser(username string) (User, error)
GetUsers() ([]User, error)
GetSession(token string) (Session, error)
Login(username, password string) (Session, error)
}
func UserForSession(as AuthenticatorService, token string) (User, error) {
session, err := as.GetSession(token)
if err != nil {
return nil, err
}
user, err := as.GetUser(session.GetUsername())
if err != nil {
return nil, err
}
return user, nil
}
func New(host string) AuthenticatorService {
if host == ":memory:" {
return exampleMemoryUsers
}
return &LDAPAuthService{host}
}

@ -0,0 +1,138 @@
package auth
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"path"
"time"
)
type LDAPUser struct {
Username string `json:"username"`
NumericId int `json:"id"`
Name string `json:"name"`
Surname string `json:"surname"`
Email string `json:"email"`
Role string `json:"role"`
Gecos string `json:"gecos"`
}
func (u LDAPUser) GetUsername() string {
return u.Username
}
func (u LDAPUser) GetName() string {
return u.Name
}
func (u LDAPUser) GetSurname() string {
return u.Surname
}
func (u LDAPUser) GetFullName() string {
return u.Gecos
}
type SimpleSession struct {
Token string `json:"token"`
Username string `json:"username"`
CreatedOn time.Time `json:"createdOn"`
}
func (s SimpleSession) GetUsername() string {
return s.Username
}
func (s SimpleSession) GetToken() string {
return s.Token
}
type LDAPAuthService struct {
Host string
}
func (a *LDAPAuthService) doGetRequest(url string, response interface{}) error {
req, err := http.NewRequest(
"GET", path.Join(a.Host, "ldap", url), bytes.NewBuffer([]byte("")),
)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
return json.NewDecoder(res.Body).Decode(response)
}
func (a *LDAPAuthService) doPostRequest(url string, request interface{}, response interface{}) error {
jsonStr, err := json.Marshal(request)
if err != nil {
return err
}
req, err := http.NewRequest("POST", path.Join(a.Host, "ldap", url), bytes.NewBuffer(jsonStr))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
return json.NewDecoder(res.Body).Decode(response)
}
func (a *LDAPAuthService) GetUser(username string) (User, error) {
var user LDAPUser
if err := a.doGetRequest(fmt.Sprintf("/user/%s", username), &user); err != nil {
return nil, err
}
return &user, nil
}
func (a *LDAPAuthService) GetUsers() ([]User, error) {
ldapUsers := []*LDAPUser{}
if err := a.doGetRequest(fmt.Sprintf("/users"), &ldapUsers); err != nil {
return nil, err
}
users := make([]User, len(ldapUsers))
for i, u := range ldapUsers {
users[i] = u
}
return users, nil
}
func (a *LDAPAuthService) GetSession(token string) (Session, error) {
var response SimpleSession
if err := a.doGetRequest(fmt.Sprintf("/session/%s", token), &response); err != nil {
return nil, err
}
return &response, nil
}
func (a *LDAPAuthService) Login(username, password string) (Session, error) {
body := map[string]interface{}{
"username": username,
"password": password,
}
var response SimpleSession
if err := a.doPostRequest(fmt.Sprintf("/login"), body, &response); err != nil {
return nil, err
}
return &response, nil
}

@ -0,0 +1,114 @@
package auth
import (
"fmt"
"git.phc.dm.unipi.it/phc/website/util"
)
var exampleMemoryUsers = &Memory{
Users: map[string]*MemoryUser{
"aziis98": {
Username: "aziis98",
Name: "Antonio",
Surname: "De Lucreziis",
Password: "123",
},
"bachoseven": {
Username: "bachoseven",
Name: "Francesco",
Surname: "Minnocci",
Password: "234",
},
},
Sessions: map[string]*MemorySession{},
}
type MemoryUser struct {
Username string `json:"username"`
Name string `json:"name"`
Surname string `json:"surname"`
Password string `json:"-"`
}
func (u *MemoryUser) GetUsername() string {
return u.Username
}
func (u *MemoryUser) GetName() string {
return u.Name
}
func (u *MemoryUser) GetSurname() string {
return u.Surname
}
func (u *MemoryUser) GetFullName() string {
return u.Name + " " + u.Surname
}
type MemorySession struct {
Username string
Token string
}
func (s *MemorySession) GetUsername() string {
return s.Username
}
func (s *MemorySession) GetToken() string {
return s.Token
}
type Memory struct {
Users map[string]*MemoryUser
Sessions map[string]*MemorySession
}
func (m *Memory) GetUser(username string) (User, error) {
user, ok := m.Users[username]
if !ok {
return nil, fmt.Errorf(`no user with that username`)
}
return user, nil
}
func (m *Memory) GetUsers() ([]User, error) {
users := make([]User, len(m.Users))
i := 0
for _, u := range m.Users {
users[i] = u
i++
}
return users, nil
}
func (m *Memory) GetSession(token string) (Session, error) {
session, ok := m.Sessions[token]
if !ok {
return nil, fmt.Errorf(`invalid session token`)
}
return session, nil
}
func (m *Memory) Login(username string, password string) (Session, error) {
user, err := m.GetUser(username)
if err != nil {
return nil, err
}
memUser := user.(*MemoryUser)
if memUser.Password != password {
return nil, fmt.Errorf(`invalid credentials`)
}
session := &MemorySession{username, util.GenerateRandomString(15)}
m.Sessions[session.Token] = session
return session, nil
}

@ -18,6 +18,8 @@ var Email string
var UserPagesBaseUrl string var UserPagesBaseUrl string
var AuthServiceHost string
func loadEnv(target *string, name, defaultValue string) { func loadEnv(target *string, name, defaultValue string) {
value := os.Getenv(name) value := os.Getenv(name)
if len(strings.TrimSpace(value)) == 0 { if len(strings.TrimSpace(value)) == 0 {
@ -30,17 +32,22 @@ func loadEnv(target *string, name, defaultValue string) {
func Load() { func Load() {
godotenv.Load() godotenv.Load()
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
// Production
loadEnv(&Mode, "MODE", "production") loadEnv(&Mode, "MODE", "production")
loadEnv(&Host, "HOST", "localhost:8080") loadEnv(&Host, "HOST", "localhost:8080")
// Services
loadEnv(&GitUrl, "GIT_URL", "https://git.example.org") loadEnv(&GitUrl, "GIT_URL", "https://git.example.org")
loadEnv(&ChatUrl, "CHAT_URL", "https://chat.example.org") loadEnv(&ChatUrl, "CHAT_URL", "https://chat.example.org")
loadEnv(&Email, "EMAIL", "mail@example.org") loadEnv(&Email, "EMAIL", "mail@example.org")
// Poisson
loadEnv(&UserPagesBaseUrl, "USER_PAGES_BASE_URL", "https://poisson.phc.dm.unipi.it/~") loadEnv(&UserPagesBaseUrl, "USER_PAGES_BASE_URL", "https://poisson.phc.dm.unipi.it/~")
// AuthService
loadEnv(&AuthServiceHost, "AUTH_SERVICE_HOST", "http://localhost:3535")
} }
func Object() util.H { func Object() util.H {
@ -53,5 +60,7 @@ func Object() util.H {
"Email": Email, "Email": Email,
"UserPagesBaseUrl": UserPagesBaseUrl, "UserPagesBaseUrl": UserPagesBaseUrl,
"AuthServiceHost": AuthServiceHost,
} }
} }

@ -1,9 +1,12 @@
package main package main
import ( import (
"fmt"
"html/template" "html/template"
"time"
"git.phc.dm.unipi.it/phc/website/articles" "git.phc.dm.unipi.it/phc/website/articles"
"git.phc.dm.unipi.it/phc/website/auth"
"git.phc.dm.unipi.it/phc/website/config" "git.phc.dm.unipi.it/phc/website/config"
"git.phc.dm.unipi.it/phc/website/templates" "git.phc.dm.unipi.it/phc/website/templates"
"git.phc.dm.unipi.it/phc/website/util" "git.phc.dm.unipi.it/phc/website/util"
@ -13,6 +16,15 @@ import (
"github.com/gofiber/redirect/v2" "github.com/gofiber/redirect/v2"
) )
func UserMiddleware(as auth.AuthenticatorService) fiber.Handler {
return func(c *fiber.Ctx) error {
token := c.Cookies("session-token")
user, _ := auth.UserForSession(as, token)
c.Locals("user", user)
return c.Next()
}
}
func main() { func main() {
config.Load() config.Load()
@ -29,6 +41,9 @@ func main() {
// Static content // Static content
app.Static("/public/", "./public") app.Static("/public/", "./public")
authService := auth.New(config.AuthServiceHost)
app.Use(UserMiddleware(authService))
// Templates & Renderer // Templates & Renderer
renderer := templates.NewRenderer( renderer := templates.NewRenderer(
"./views/", "./views/",
@ -51,7 +66,9 @@ func main() {
localView := view localView := view
app.Get(route, func(c *fiber.Ctx) error { app.Get(route, func(c *fiber.Ctx) error {
c.Type("html") c.Type("html")
return renderer.Render(c, localView, util.H{}) return renderer.Render(c, localView, util.H{
"User": c.Locals("user"),
})
}) })
} }
@ -72,6 +89,7 @@ func main() {
c.Type("html") c.Type("html")
return renderer.Render(c, "storia.html", util.H{ return renderer.Render(c, "storia.html", util.H{
"User": c.Locals("user"),
"Storia": storia, "Storia": storia,
}) })
}) })
@ -81,6 +99,7 @@ func main() {
c.Type("html") c.Type("html")
return renderer.Render(c, "appunti.html", util.H{ return renderer.Render(c, "appunti.html", util.H{
"User": c.Locals("user"),
"Query": searchQuery, "Query": searchQuery,
}) })
}) })
@ -93,10 +112,50 @@ func main() {
c.Type("html") c.Type("html")
return renderer.Render(c, "news.html", util.H{ return renderer.Render(c, "news.html", util.H{
"User": c.Locals("user"),
"Articles": articles, "Articles": articles,
}) })
}) })
app.Post("/login", func(c *fiber.Ctx) error {
var loginForm struct {
Provider string `form:"provider"`
Username string `form:"username"`
Password string `form:"password"`
}
if err := c.BodyParser(&loginForm); err != nil {
return err
}
session, err := authService.Login(loginForm.Username, loginForm.Password)
if err != nil {
return err
}
inThreeDays := time.Now().Add(3 * 24 * time.Hour)
c.Cookie(&fiber.Cookie{
Name: "session-token",
Path: "/",
Value: session.GetToken(),
Expires: inThreeDays,
})
return c.Redirect("/profilo")
})
app.Get("/profilo", func(c *fiber.Ctx) error {
user, ok := c.Locals("user").(auth.User)
if !ok || user == nil {
return fmt.Errorf(`no logged in user`)
}
c.Type("html")
return renderer.Render(c, "profilo.html", util.H{
"User": c.Locals("user"),
})
})
app.Get("/news/:article", func(c *fiber.Ctx) error { app.Get("/news/:article", func(c *fiber.Ctx) error {
articleID := c.Params("article") articleID := c.Params("article")
@ -112,6 +171,7 @@ func main() {
c.Type("html") c.Type("html")
return renderer.Render(c, "news-base.html", util.H{ return renderer.Render(c, "news-base.html", util.H{
"User": c.Locals("user"),
"Article": article, "Article": article,
"ContentHTML": template.HTML(html), "ContentHTML": template.HTML(html),
}) })

@ -322,13 +322,15 @@ section {
.card .title { .card .title {
font-size: 22px; font-size: 22px;
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
padding-bottom: 0.5rem;
} }
.card .date { .card .date {
font-size: 15px; font-size: 15px;
color: var(--card-date); color: var(--card-date);
margin-bottom: 0.75rem; padding-bottom: 0.5rem;
} }
.card .description { .card .description {
@ -341,7 +343,7 @@ section {
flex-direction: row; flex-direction: row;
gap: 0 0.5rem; gap: 0 0.5rem;
margin-top: 0.5rem; padding-top: 0.5rem;
} }
.tags .tag { .tags .tag {
@ -393,9 +395,10 @@ p,
ul, ul,
ol, ol,
li { li {
margin: 0.5rem 0; margin: 0;
width: 70ch; width: 70ch;
max-width: 100%; max-width: 100%;
line-height: 1.8;
} }
ul, ul,
@ -832,6 +835,10 @@ form .field-set input {
/* Rendered Markdown */ /* Rendered Markdown */
.news-content p {
margin: 0.5rem 0;
}
.news-content { .news-content {
display: block; display: block;
} }

@ -0,0 +1 @@
package util

@ -0,0 +1,18 @@
package util
import (
"crypto/rand"
"encoding/base64"
"log"
)
func GenerateRandomString(n int) string {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
log.Fatal(err)
}
return base64.URLEncoding.EncodeToString(b)
}

@ -4,10 +4,10 @@
{{define "body"}} {{define "body"}}
<section> <section>
<h2> <h1>
<i class="fas fa-book"></i> <i class="fas fa-book"></i>
Raccolta degli Appunti Raccolta degli Appunti
</h2> </h1>
<p> <p>
Questa è la raccolta degli appunti presenti su Poisson. Cerca il titolo della dispensa, il nome e cognome o l'username dell'autore oppure scrivi il nome del corso rispetto a cui filtrare. Altrimenti in cima compariranno gli appunti più "gettonati". Questa è la raccolta degli appunti presenti su Poisson. Cerca il titolo della dispensa, il nome e cognome o l'username dell'autore oppure scrivi il nome del corso rispetto a cui filtrare. Altrimenti in cima compariranno gli appunti più "gettonati".
</p> </p>

@ -28,8 +28,9 @@
</div> </div>
<div class="date">yyyy-mm-dd</div> <div class="date">yyyy-mm-dd</div>
<div class="description"> <div class="description">
<p>
much doge, ipsum dolor sit amet consectetur adipisicing elit. Quis nemo aperiam, voluptas quam alias esse sed natus tempore suscipit fugiat sit delectus exercitationem numquam ipsum assumenda recusandae consequatur... much doge, ipsum dolor sit amet consectetur adipisicing elit. Quis nemo aperiam, voluptas quam alias esse sed natus tempore suscipit fugiat sit delectus exercitationem numquam ipsum assumenda recusandae consequatur...
</p>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
@ -38,8 +39,9 @@
</div> </div>
<div class="date">yyyy-mm-dd</div> <div class="date">yyyy-mm-dd</div>
<div class="description"> <div class="description">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quis nemo aperiam, voluptas quam alias esse sed natus tempore suscipit fugiat sit delectus exercitationem numquam ipsum assumenda recusandae consequatur... Lorem ipsum dolor sit amet consectetur adipisicing elit. Quis nemo aperiam, voluptas quam alias esse sed natus tempore suscipit fugiat sit delectus exercitationem numquam ipsum assumenda recusandae consequatur...
</p>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
@ -48,8 +50,9 @@
</div> </div>
<div class="date">yyyy-mm-dd</div> <div class="date">yyyy-mm-dd</div>
<div class="description"> <div class="description">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quis nemo aperiam, voluptas quam alias esse sed natus tempore suscipit fugiat sit delectus exercitationem numquam ipsum assumenda recusandae consequatur... Lorem ipsum dolor sit amet consectetur adipisicing elit. Quis nemo aperiam, voluptas quam alias esse sed natus tempore suscipit fugiat sit delectus exercitationem numquam ipsum assumenda recusandae consequatur...
</p>
</div> </div>
</div> </div>
</div> </div>

@ -4,10 +4,10 @@
{{define "body"}} {{define "body"}}
<section> <section>
<h2> <h1>
<i class="fas fa-link"></i> <i class="fas fa-link"></i>
Link Utili Link Utili
</h2> </h1>
<p class="center"> <p class="center">
Questo è un elenco di alcuni indirizzi potenzialmente utili Questo è un elenco di alcuni indirizzi potenzialmente utili
</p> </p>
@ -19,7 +19,9 @@
<i class="fas fa-external-link-alt"></i> <i class="fas fa-external-link-alt"></i>
</div> </div>
<div class="description"> <div class="description">
<p>
Lista di link relativi alle attività dell'aula studenti del Dipartimento di Matematica Lista di link relativi alle attività dell'aula studenti del Dipartimento di Matematica
</p>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
@ -28,7 +30,9 @@
<i class="fas fa-external-link-alt"></i> <i class="fas fa-external-link-alt"></i>
</div> </div>
<div class="description"> <div class="description">
<p>
Sito per chiedere il recupero/reset delle proprie credenziali Poisson; le matricole possono inserire direttamente le loro credenziali di Alice ed ottenere quelle del loro account su Poisson. Sito per chiedere il recupero/reset delle proprie credenziali Poisson; le matricole possono inserire direttamente le loro credenziali di Alice ed ottenere quelle del loro account su Poisson.
</p>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
@ -37,7 +41,9 @@
<i class="fas fa-external-link-alt"></i> <i class="fas fa-external-link-alt"></i>
</div> </div>
<div class="description"> <div class="description">
<p>
Questo sito ti permette di disegnare "a mano" il simbolo che cerchi e trovare il suo corrispondente comando in LaTex. Questo sito ti permette di disegnare "a mano" il simbolo che cerchi e trovare il suo corrispondente comando in LaTex.
</p>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
@ -46,7 +52,9 @@
<i class="fas fa-external-link-alt"></i> <i class="fas fa-external-link-alt"></i>
</div> </div>
<div class="description"> <div class="description">
<p>
Un semplice shell script che permette di stampare un file in una stampante del Dipartimento di Matematica da una qualsiasi shell Unix (Linux, MacOS, BSD...). Funziona anche da remoto! Un semplice shell script che permette di stampare un file in una stampante del Dipartimento di Matematica da una qualsiasi shell Unix (Linux, MacOS, BSD...). Funziona anche da remoto!
</p>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
@ -55,7 +63,9 @@
<i class="fas fa-external-link-alt"></i> <i class="fas fa-external-link-alt"></i>
</div> </div>
<div class="description"> <div class="description">
<p>
Sei stanco del pessimo font di Wikipedia? Finalmente potrai leggere i <a href="https://en.wikipedia.org/wiki/Abstract_nonsense">tuoi</a> <a href="https://en.wikipedia.org/wiki/New_Math">articoli</a> <a href="https://en.wikipedia.org/wiki/%C3%89variste_Galois#Final_days">preferiti</a> di Wikipedia con un typesetting in stile LaTex! Sei stanco del pessimo font di Wikipedia? Finalmente potrai leggere i <a href="https://en.wikipedia.org/wiki/Abstract_nonsense">tuoi</a> <a href="https://en.wikipedia.org/wiki/New_Math">articoli</a> <a href="https://en.wikipedia.org/wiki/%C3%89variste_Galois#Final_days">preferiti</a> di Wikipedia con un typesetting in stile LaTex!
</p>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
@ -64,7 +74,9 @@
<i class="fas fa-external-link-alt"></i> <i class="fas fa-external-link-alt"></i>
</div> </div>
<div class="description"> <div class="description">
<p>
La homepage del corso di studi all'interno del sito ufficiale del Dipartimento di Matematica La homepage del corso di studi all'interno del sito ufficiale del Dipartimento di Matematica
</p>
</div> </div>
</div> </div>
</div> </div>

@ -4,10 +4,10 @@
{{define "body"}} {{define "body"}}
<section> <section>
<h2> <h1>
<i class="fas fa-user"></i> <i class="fas fa-user"></i>
Account di Poisson Account di Poisson
</h2> </h1>
<div class="card-list"> <div class="card-list">
<form class="card" action="/login" method="POST"> <form class="card" action="/login" method="POST">
<div class="title"> <div class="title">
@ -15,10 +15,13 @@
Accedi Accedi
</div> </div>
<p> <p>
Inserisci le tue credenziali di Poisson per accedere. Inserisci le tue credenziali di Poisson per accedere
</p> </p>
<div class="field-set"> <div class="field-set">
<!-- <label for="login-provider">Provider:</label> -->
<input type="hidden" name="provider" id="login-provider" value="poisson-ldap">
<label for="login-username">Username:</label> <label for="login-username">Username:</label>
<input type="text" name="username" id="login-username"> <input type="text" name="username" id="login-username">
@ -35,11 +38,10 @@
<section> <section>
<h2>Ottenere un account</h2> <h2>Ottenere un account</h2>
<p> <p>
Se vuoi ottenere un account compila* il <a href="#">modulo di richiesta</a> e portacelo in PHC o inviacelo via Dal 2022 in nuovi utenti hanno bisogno di compilare un modulo se vogliono ottenere un account<sup>1</sup>, scarica il <a href="#">modulo di richiesta</a> e portacelo in PHC o inviacelo via email all'indirizzo <a href="mailto:{{ .Config.Email }}">{{ .Config.Email }}</a>.
email all'indirizzo <a href="mailto:{{ .Config.Email }}">{{ .Config.Email }}</a>.
</p> </p>
<p> <p>
*In realtà il modulo ancora non esiste 1: In realtà il modulo ancora non esiste
</p> </p>
</section> </section>
@ -47,7 +49,7 @@
<h2>Recupero credenziali</h2> <h2>Recupero credenziali</h2>
<p> <p>
Per il recupero credenziali vieni direttamente al PHC a parlarne con calma con noi altrimenti puoi inviaci una Per il recupero credenziali vieni direttamente al PHC a parlarne con calma con noi altrimenti puoi inviaci una
email all'indirizzo <a href="mailto:{{ .Config.Email }}">{{ .Config.Email }}</a>. email all'indirizzo <a href="mailto:{{ .Config.Email }}">{{ .Config.Email }}</a> e poi recuperare le nuove credenziali sul sito <a href="https://credenziali.phc.dm.unipi.it/">credenziali.phc.dm.unipi.it</a>.
</p> </p>
</section> </section>
{{end}} {{end}}

@ -4,10 +4,10 @@
{{define "body"}} {{define "body"}}
<section> <section>
<h2> <h1>
<i class="far fa-newspaper"></i> <i class="far fa-newspaper"></i>
Notizie Importanti Notizie Importanti
</h2> </h1>
<div class="card-list"> <div class="card-list">
{{ range .Articles }} {{ range .Articles }}
{{ if .HasTag "important" }} {{ if .HasTag "important" }}
@ -18,7 +18,9 @@
</a> </a>
</div> </div>
<div class="date">{{ .PublishDate.Format "2006/01/02" }}</div> <div class="date">{{ .PublishDate.Format "2006/01/02" }}</div>
<div class="description">{{ .Description }}</div> <div class="description">
<p>{{ .Description }}</p>
</div>
<div class="tags"> <div class="tags">
{{ range .Tags }} {{ range .Tags }}
<span class="tag">{{ . }}</span> <span class="tag">{{ . }}</span>
@ -45,7 +47,9 @@
</a> </a>
</div> </div>
<div class="date">{{ .PublishDate.Format "2006/01/02" }}</div> <div class="date">{{ .PublishDate.Format "2006/01/02" }}</div>
<div class="description">{{ .Description }}</div> <div class="description">
<p>{{ .Description }}</p>
</div>
<div class="tags"> <div class="tags">
{{ range .Tags }} {{ range .Tags }}
<span class="tag">{{ . }}</span> <span class="tag">{{ . }}</span>

@ -76,7 +76,7 @@
</div> </div>
{{if .User}} {{if .User}}
<div class="nav-item"> <div class="nav-item">
<a class="nav-element" href="/profile">@{{ .User }}</a> <a class="nav-element" href="/profilo">@{{ .User.Username }}</a>
</div> </div>
{{else}} {{else}}
<div class="nav-item"> <div class="nav-item">

@ -0,0 +1,17 @@
{{template "base" .}}
{{define "title"}}Profilo @{{ .User.Username }} &bull; PHC{{end}}
{{define "body"}}
<section>
<h1>Profilo di @{{ .User.Username }}</h1>
<h2>Impostazioni</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quae earum amet delectus cumque obcaecati minus quos aliquid fugiat reprehenderit voluptatum?
</p>
<h2>Recupero Credenziali Poisson</h2>
<p>
Per il recupero credenziali vieni direttamente al PHC a parlarne con calma con noi altrimenti puoi inviaci una email all'indirizzo <a href="mailto:{{ .Config.Email }}">{{ .Config.Email }}</a> e poi recuperare le nuove credenziali sul sito <a href="https://credenziali.phc.dm.unipi.it/">credenziali.phc.dm.unipi.it</a>.
</p>
</section>
{{end}}

@ -5,10 +5,10 @@
<script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js"></script> <script src="https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js"></script>
<script src="/public/js/utenti.min.js"></script> <script src="/public/js/utenti.min.js"></script>
<section x-data="utenti"> <section x-data="utenti">
<h2> <h1>
<i class="fas fa-users"></i> <i class="fas fa-users"></i>
Lista degli Utenti Lista degli Utenti
</h2> </h1>
<p> <p>
Questa è la lista di tutti gli utenti con un account su Poisson. Scrivi nome, cognome o Questa è la lista di tutti gli utenti con un account su Poisson. Scrivi nome, cognome o
username di un utente per filtrare la lista in tempo reale. Altrimenti di base in cima username di un utente per filtrare la lista in tempo reale. Altrimenti di base in cima

Loading…
Cancel
Save