mirror of
https://github.com/Steffo99/festa.git
synced 2024-12-22 14:44:21 +00:00
I actually made a lot of stuff without committing
This commit is contained in:
parent
79063b6735
commit
fb96441289
11 changed files with 262 additions and 139 deletions
7
.env
7
.env
|
@ -1,7 +0,0 @@
|
|||
# Environment variables declared in this file are automatically made available to Prisma.
|
||||
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
|
||||
|
||||
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB (Preview).
|
||||
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
|
||||
|
||||
DATABASE_URL="postgresql://steffo@localhost/festa?host=/var/run/postgresql"
|
15
.vscode/settings.json
vendored
Normal file
15
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"files.exclude": {
|
||||
// Version control
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
// Node
|
||||
"node_modules": true,
|
||||
".next": true,
|
||||
"yarn.lock": true,
|
||||
}
|
||||
}
|
|
@ -13,7 +13,8 @@
|
|||
"next": "12.1.6",
|
||||
"prisma": "^3.14.0",
|
||||
"react": "18.1.0",
|
||||
"react-dom": "18.1.0"
|
||||
"react-dom": "18.1.0",
|
||||
"react-telegram-login": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "17.0.35",
|
||||
|
|
|
@ -3,7 +3,7 @@ import type { NextPage } from 'next'
|
|||
const Page: NextPage = () => {
|
||||
return (
|
||||
<div>
|
||||
sus
|
||||
wrong page bro
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
47
pages/party/[slug].tsx
Normal file
47
pages/party/[slug].tsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import type { NextPage, NextPageContext } from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import TelegramLoginButton from 'react-telegram-login'
|
||||
import * as Telegram from "../../utils/telegram"
|
||||
|
||||
interface PageProps {
|
||||
userData: Telegram.LoginData | null
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context: NextPageContext): Promise<{props: PageProps}> {
|
||||
const props: PageProps = {
|
||||
userData: null,
|
||||
}
|
||||
|
||||
const token = process.env.BOT_TOKEN
|
||||
if(token === undefined) {
|
||||
throw new Error("BOT_TOKEN is not set on the server-side, cannot perform login validation.")
|
||||
}
|
||||
|
||||
if(context.query.hash !== undefined) {
|
||||
const loginResponse = new Telegram.LoginResponse(context.query)
|
||||
if(loginResponse.isRecent() && loginResponse.isValid(process.env.BOT_TOKEN)) {
|
||||
props.userData = loginResponse.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
return {props}
|
||||
}
|
||||
|
||||
const Page: NextPage<PageProps> = ({userData}) => {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{userData ?
|
||||
JSON.stringify(userData)
|
||||
:
|
||||
<TelegramLoginButton
|
||||
dataAuthUrl={router.asPath}
|
||||
botName="festaappbot"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Page
|
|
@ -32,6 +32,17 @@ model Event {
|
|||
vehicles Vehicle[]
|
||||
}
|
||||
|
||||
/// A user is a person who is using the Festa website, who logged in via Telegram.
|
||||
model User {
|
||||
id BigInt @id
|
||||
firstName String
|
||||
lastName String?
|
||||
username String?
|
||||
photoUrl String?
|
||||
lastAuthDate DateTime
|
||||
hash String
|
||||
}
|
||||
|
||||
/// A partecipant is a person who may or may not partecipate to the event.
|
||||
model Partecipant {
|
||||
id Int @id @default(autoincrement())
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
.container {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
padding: 4rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid #eaeaea;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 4rem 0;
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 1em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,22 @@
|
|||
html,
|
||||
body {
|
||||
html {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
"incremental": true,
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
|
|
150
utils/telegram.ts
Normal file
150
utils/telegram.ts
Normal file
|
@ -0,0 +1,150 @@
|
|||
import nodecrypto from "crypto"
|
||||
|
||||
/**
|
||||
* The validated user data serialized by the server.
|
||||
*/
|
||||
export interface LoginData {
|
||||
id: number
|
||||
first_name: string
|
||||
last_name: string | null
|
||||
username: string | null
|
||||
photo_url: string | null
|
||||
lang: string | null
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The response sent by Telegram after a login.
|
||||
*/
|
||||
export class LoginResponse {
|
||||
id: number
|
||||
first_name: string
|
||||
last_name?: string
|
||||
username?: string
|
||||
photo_url?: string
|
||||
auth_date: number
|
||||
hash: string
|
||||
lang?: string
|
||||
|
||||
/**
|
||||
* Construct a new {@link LoginResponse} from a query string object as returned by Next.js.
|
||||
*
|
||||
* @param queryObj The query string object, from `context.query`.
|
||||
*/
|
||||
constructor(queryObj: {[_: string]: string | string[]}) {
|
||||
if(typeof queryObj.id === "object") {
|
||||
throw new Error("Multiple `id` parameters specified in the query string, cannot construct LoginResponse.")
|
||||
}
|
||||
if(typeof queryObj.first_name === "object") {
|
||||
throw new Error("Multiple `first_name` parameters specified in the query string, cannot construct LoginResponse.")
|
||||
}
|
||||
if(typeof queryObj.last_name === "object") {
|
||||
throw new Error("Multiple `last_name` parameters specified in the query string, cannot construct LoginResponse.")
|
||||
}
|
||||
if(typeof queryObj.username === "object") {
|
||||
throw new Error("Multiple `username` parameters specified in the query string, cannot construct LoginResponse.")
|
||||
}
|
||||
if(typeof queryObj.photo_url === "object") {
|
||||
throw new Error("Multiple `photo_url` parameters specified in the query string, cannot construct LoginResponse.")
|
||||
}
|
||||
if(typeof queryObj.auth_date === "object") {
|
||||
throw new Error("Multiple `auth_date` parameters specified in the query string, cannot construct LoginResponse.")
|
||||
}
|
||||
if(typeof queryObj.hash === "object") {
|
||||
throw new Error("Multiple `hash` parameters specified in the query string, cannot construct LoginResponse.")
|
||||
}
|
||||
if(typeof queryObj.lang === "object") {
|
||||
throw new Error("Multiple `hash` parameters specified in the query string, cannot construct LoginResponse.")
|
||||
}
|
||||
|
||||
this.id = parseInt(queryObj.id)
|
||||
this.first_name = queryObj.first_name
|
||||
this.last_name = queryObj.last_name
|
||||
this.username = queryObj.username
|
||||
this.photo_url = queryObj.photo_url
|
||||
this.auth_date = parseInt(queryObj.auth_date)
|
||||
this.hash = queryObj.hash
|
||||
this.lang = queryObj.lang
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize this response into a {@link LoginData} object, which can be passed to the client by Next.js.
|
||||
*
|
||||
* @returns The {@link LoginData} object.
|
||||
*/
|
||||
serialize(): LoginData {
|
||||
return {
|
||||
id: this.id ?? null,
|
||||
first_name: this.first_name ?? null,
|
||||
last_name: this.last_name ?? null,
|
||||
username: this.username ?? null,
|
||||
photo_url: this.photo_url ?? null,
|
||||
lang: this.lang ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify a {@link LoginResponse} in [the format required to verify a Telegram Login](https://core.telegram.org/widgets/login#checking-authorization).
|
||||
*
|
||||
* @param data The data to encode.
|
||||
* @returns The stringified data.
|
||||
*/
|
||||
stringify(): string {
|
||||
const string = Object.entries(this)
|
||||
.filter(([key, _]) => key !== "hash")
|
||||
.filter(([_, value]) => value !== undefined)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.sort()
|
||||
.join("\n")
|
||||
return string
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the `auth_date` of the response is recent: it must be in the past, but within `maxSeconds` from the current date.
|
||||
*
|
||||
* @param maxSeconds The maximum number of milliseconds that may pass after authentication for the response to be considered valid; defaults to `300000`, 5 minutes.
|
||||
* @returns `true` if the response can be considered recent, `false` otherwise.
|
||||
*/
|
||||
isRecent(maxSeconds: number = 300000): boolean {
|
||||
const diff = new Date().getTime() - new Date(this.auth_date * 1000).getTime()
|
||||
return 0 < diff && diff <= maxSeconds
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the "`hash`" of a Telegram Login using [this procedure](https://core.telegram.org/widgets/login#checking-authorization).
|
||||
*
|
||||
* @param token The bot token used to validate the signature.
|
||||
* @returns The calculated value of the `hash` {@link LoginResponse} parameter.
|
||||
*/
|
||||
hmac(token: string): string {
|
||||
const key = hashToken(token)
|
||||
const hmac = nodecrypto.createHmac("sha256", key)
|
||||
hmac.update(this.stringify())
|
||||
return hmac.digest("hex")
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a Telegram Login using [this procedure](https://core.telegram.org/widgets/login#checking-authorization).
|
||||
*
|
||||
* @param token The bot token used to validate the signature.
|
||||
* @returns `true` if the validation is successful, `false` otherwise.
|
||||
*/
|
||||
isValid(token: string): boolean {
|
||||
const client = this.hmac(token)
|
||||
const server = this.hash
|
||||
return client === server
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a Telegram bot token using SHA-256.
|
||||
*
|
||||
* @param token The bot token to hash.
|
||||
* @returns The hex digest of the hash.
|
||||
*/
|
||||
function hashToken(token: string): Buffer {
|
||||
const hash = nodecrypto.createHash("sha256")
|
||||
hash.update(token)
|
||||
return hash.digest()
|
||||
}
|
18
yarn.lock
18
yarn.lock
|
@ -1406,7 +1406,7 @@ prisma@^3.14.0:
|
|||
dependencies:
|
||||
"@prisma/engines" "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a"
|
||||
|
||||
prop-types@^15.8.1:
|
||||
prop-types@^15.6.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
@ -1438,6 +1438,13 @@ react-is@^16.13.1:
|
|||
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-telegram-login@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/react-telegram-login/-/react-telegram-login-1.1.2.tgz#28b9bdd68bb2710afca19354ac1f9428092836f0"
|
||||
integrity sha512-pDP+bvfaklWgnK5O6yvZnIwgky0nnYUU6Zhk0EjdMSkPsLQoOzZRsXIoZnbxyBXhi7346bsxMH+EwwJPTxClDw==
|
||||
dependencies:
|
||||
react "^16.13.1"
|
||||
|
||||
react@18.1.0:
|
||||
version "18.1.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-18.1.0.tgz#6f8620382decb17fdc5cc223a115e2adbf104890"
|
||||
|
@ -1445,6 +1452,15 @@ react@18.1.0:
|
|||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
react@^16.13.1:
|
||||
version "16.14.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d"
|
||||
integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
|
|
Loading…
Reference in a new issue