-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from bokub/cloudflare
Migrate from Vercel to Cloudflare Pages
- Loading branch information
Showing
16 changed files
with
732 additions
and
211 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,5 @@ node_modules | |
dist | ||
.vercel | ||
test.md | ||
.wrangler | ||
.dev.vars |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import axios from 'axios'; | ||
import * as qs from 'qs'; | ||
import pino from 'pino'; | ||
import { z } from 'zod'; | ||
import { getAPIToken } from '../../lib/token'; | ||
import { dataPoints, dataURLs } from '../../lib/url'; | ||
import { isTokenValid } from '../../lib/auth'; | ||
import { Env } from '../../lib/env'; | ||
|
||
const logger = pino(); | ||
|
||
const schema = z.object({ | ||
type: z.enum(dataPoints), | ||
prm: z.string().length(14), | ||
start: z.string().regex(/20[0-9]{2}-[0-9]{2}-[0-9]{2}/), | ||
end: z.string().regex(/20[0-9]{2}-[0-9]{2}-[0-9]{2}/), | ||
}); | ||
|
||
export const onRequest: PagesFunction<Env> = async ({ request: req, params, env }) => { | ||
if (req.method === 'OPTIONS') { | ||
return new Response(null, { | ||
status: 200, | ||
}); | ||
} | ||
if (req.method !== 'GET') { | ||
return Response.json({ status: 405, message: 'Seule la méthode GET est autorisée' }, { status: 405 }); | ||
} | ||
|
||
const { BASE_URL, JWT_SECRET } = env; | ||
const { searchParams } = new URL(req.url); | ||
|
||
// Validate input | ||
const input = schema.safeParse({ | ||
type: params.type, | ||
prm: searchParams.get('prm'), | ||
start: searchParams.get('start'), | ||
end: searchParams.get('end'), | ||
}); | ||
|
||
if (input.success === false) { | ||
return Response.json( | ||
{ | ||
status: 400, | ||
message: 'Paramètres invalides', | ||
error: input.error, | ||
}, | ||
{ status: 400 } | ||
); | ||
} | ||
|
||
const { type, prm, start, end } = input.data; | ||
|
||
// Validate user token | ||
const authHeader = req.headers.get('Authorization'); | ||
const userToken = authHeader?.split(' ')[1]; | ||
if (!userToken) { | ||
return Response.json( | ||
{ status: 400, message: "Le header 'Authorization' est manquant ou invalide" }, | ||
{ status: 400 } | ||
); | ||
} | ||
if (!(await isTokenValid(userToken, prm, JWT_SECRET))) { | ||
return Response.json( | ||
{ status: 401, message: "Votre token est invalide ou ne permet pas d'accéder à ce PRM" }, | ||
{ status: 401 } | ||
); | ||
} | ||
|
||
// Get Enedis token | ||
let apiToken: string; | ||
try { | ||
apiToken = await getAPIToken(env); | ||
} catch (e) { | ||
logger.error({ message: 'cannot refresh token', error: e }); | ||
return Response.json( | ||
{ status: 500, message: 'Impossible de rafraîchir le token. Réessayez plus tard' }, | ||
{ status: 500 } | ||
); | ||
} | ||
|
||
// Fetch data | ||
try { | ||
const response = await fetch( | ||
`${BASE_URL}/${dataURLs[type]}?${qs.stringify({ | ||
start, | ||
end, | ||
usage_point_id: prm, | ||
})}`, | ||
{ | ||
headers: { | ||
Accept: 'application/json', | ||
Authorization: 'Bearer ' + apiToken, | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
} | ||
); | ||
|
||
if (!response.ok) { | ||
return Response.json( | ||
{ | ||
status: response.status, | ||
message: 'The Enedis API returned an error', | ||
error: await response.json(), | ||
}, | ||
{ status: response.status } | ||
); | ||
} | ||
|
||
const data: { meter_reading: any } = await response.json(); | ||
return Response.json(data.meter_reading); | ||
} catch (e) { | ||
logger.error({ message: 'cannot call Enedis', error: e }); | ||
return Response.json({ status: 500, message: 'Erreur inconnue. Réessayez plus tard' }, { status: 500 }); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Env } from '../../lib/env'; | ||
|
||
export const onRequest: PagesFunction<Env> = async ({ request: req, env }) => { | ||
const state = 'v2_' + Array.from({ length: 8 }, () => Math.random().toString(36)[2]).join(''); | ||
const baseURI = env.SANDBOX ? 'https://ext.hml.api.enedis.fr' : 'https://mon-compte-particulier.enedis.fr'; | ||
|
||
return Response.redirect( | ||
`${baseURI}/dataconnect/v1/oauth2/authorize` + | ||
`?client_id=${env.CLIENT_ID}&state=${state}&duration=P3Y&response_type=code` | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { generateToken } from '../../lib/auth'; | ||
import { Env } from '../../lib/env'; | ||
import { z } from 'zod'; | ||
import pino from 'pino'; | ||
const logger = pino(); | ||
|
||
export const onRequest: PagesFunction<Env> = async ({ request: req, env }) => { | ||
try { | ||
const { searchParams } = new URL(req.url); | ||
const queryPRMs = z.string().parse(searchParams.get('usage_point_id')).split(/[,;]/g); | ||
|
||
const authToken = await generateToken(queryPRMs, env.JWT_SECRET); | ||
return new Response('/token', { | ||
status: 302, | ||
headers: { | ||
Location: '/token', | ||
'Set-Cookie': `conso-token=${authToken}; SameSite=Strict; Path=/`, | ||
}, | ||
}); | ||
} catch (e) { | ||
logger.error({ message: 'cannot generate Auth token', error: e }); | ||
return Response.json({ status: 500, message: 'internal server error' }, { status: 500 }); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "esnext", | ||
"module": "esnext", | ||
"lib": ["esnext"], | ||
"types": ["@cloudflare/workers-types"], | ||
"moduleResolution": "node" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { KVNamespace } from '@cloudflare/workers-types'; | ||
|
||
export interface Env { | ||
BASE_URL: string; | ||
JWT_SECRET: string; | ||
CLIENT_ID: string; | ||
CLIENT_SECRET: string; | ||
SANDBOX: string | undefined; | ||
CONSO_API: KVNamespace; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,33 +1,42 @@ | ||
import axios from 'axios'; | ||
import * as qs from 'qs'; | ||
import pino from 'pino'; | ||
import { Env } from './env'; | ||
|
||
const logger = pino(); | ||
const { BASE_URL, CLIENT_ID, CLIENT_SECRET } = process.env; | ||
|
||
let token: string; | ||
let tokenExpiration: number; | ||
const KV_KEY = 'access_token'; | ||
|
||
export async function getAPIToken(): Promise<string> { | ||
if (token && new Date().getTime() < tokenExpiration) { | ||
export async function getAPIToken(env: Env): Promise<string> { | ||
const token = await env.CONSO_API.get(KV_KEY); | ||
if (token) { | ||
return token; | ||
} | ||
|
||
const { data } = await axios({ | ||
method: 'post', | ||
url: `${BASE_URL}/oauth2/v3/token?${qs.stringify({ | ||
const response = await fetch( | ||
`${env.BASE_URL}/oauth2/v3/token?${qs.stringify({ | ||
grant_type: 'client_credentials', | ||
client_id: CLIENT_ID, | ||
client_secret: CLIENT_SECRET, | ||
client_id: env.CLIENT_ID, | ||
client_secret: env.CLIENT_SECRET, | ||
})}`, | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
}); | ||
{ | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
} | ||
); | ||
|
||
if (!response.ok) { | ||
return Promise.reject(response); | ||
} | ||
|
||
const data: { access_token: string } = await response.json(); | ||
logger.info({ message: 'token refreshed', token: data.access_token }); | ||
|
||
token = data.access_token; | ||
tokenExpiration = new Date().getTime() + 3 * 3600 * 1000; // token expiration is 3 hours later | ||
// Save in KV store | ||
await env.CONSO_API.put(KV_KEY, data.access_token, { | ||
expirationTtl: 3 * 3600, // token expiration is 3 hours later | ||
}); | ||
|
||
return token; | ||
return data.access_token; | ||
} |
Oops, something went wrong.