Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service d'authentification partagé #3177

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
801fde4
Ajoute les modèles des emails utilisés pour l'authentification
marc-rutkowski Mar 25, 2024
2ebab70
Ajoute le package @tet/auth pour centraliser les écrans de connexion …
marc-rutkowski Mar 25, 2024
3838baa
Ajoute les utilitaires permettant de partager l'authentification entr…
marc-rutkowski Mar 25, 2024
edb6f53
Ajoute les utilitaires permettant de déterminer les URLs des pages d'…
marc-rutkowski Mar 25, 2024
8a8e7bf
Ajoute un storybook pour les composants d'authentification
marc-rutkowski Mar 25, 2024
ae310eb
Ajoute le composant de vérification de robustesse du mot de passe
marc-rutkowski Mar 25, 2024
8e020f2
Ajoute le composant de saisie d'un code OTP
marc-rutkowski Mar 25, 2024
aa3a178
Ajoute le composant de demande de ré-envoi d'un e-mail d'authentifica…
marc-rutkowski Mar 25, 2024
febb5fa
Ajoute le composant de Login et ses différents sous-éléments
marc-rutkowski Mar 25, 2024
a37d0fa
Ajoute le composant de Signup et ses différents sous-éléments
marc-rutkowski Mar 25, 2024
e11bd60
Ajoute les routes/pages d'authentification et de création de compte
marc-rutkowski Mar 25, 2024
7600b60
Uniformise la version de supabase
marc-rutkowski Mar 26, 2024
8c01c44
Ajoute la commande de build du package auth
marc-rutkowski Mar 26, 2024
54ecd4a
Ajoute le package auth dans le mode dev de l'app
marc-rutkowski Mar 26, 2024
3860575
Autorise les scripts inline dans la CSP
marc-rutkowski Mar 26, 2024
9356050
Teste et corrige la fonction donnant l'url du module d'auth.
marc-rutkowski Mar 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,453 changes: 4,910 additions & 543 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
"supabase": "1.88.0"
},
"scripts": {
"dev": "concurrently --names \"api,ui,app,site\" -c \"bgMagenta.bold,bgBlue.bold,bgGreen.bold,bgYellow.bold\" \"npm run dev -w @tet/api\" \"npm run dev -w @tet/ui\" \"npm run dev -w @tet/app\" \"npm run dev -w @tet/site -- -p 3001\"",
"dev:app": "concurrently --names \"api,ui,app\" -c \"bgMagenta.bold,bgBlue.bold,bgGreen.bold\" \"npm run dev -w @tet/api\" \"npm run dev -w @tet/ui\" \"npm run dev -w @tet/app\"",
"dev": "concurrently --names \"api,ui,app,site,auth\" -c \"bgMagenta.bold,bgBlue.bold,bgGreen.bold,bgYellow.bold,bgCyan.bold\" \"npm run dev -w @tet/api\" \"npm run dev -w @tet/ui\" \"npm run dev -w @tet/app\" \"npm run dev -w @tet/site -- -p 3001\" \"npm run dev -w @tet/site -- -p 3003\"",
"dev:app": "concurrently --names \"api,ui,app,auth\" -c \"bgMagenta.bold,bgBlue.bold,bgGreen.bold,bgCyan.bold\" \"npm run dev -w @tet/api\" \"npm run dev -w @tet/ui\" \"npm run dev -w @tet/app\" \"npm run dev -w @tet/auth -- -p 3003\"",
"dev:site": "concurrently --names \"api,ui,site\" -c \"bgMagenta.bold,bgBlue.bold,bgYellow.bold\" \"npm run dev -w @tet/api\" \"npm run dev -w @tet/ui\" \"npm run dev -w @tet/site -- -p 3001\"",
"build": "if [ \"$SCALINGO_BUILD\" = \"site\" ]; then npm run build:site; else npm run build:app; fi",
"build:auth": "npm run build -w @tet/api -w @tet/ui -w @tet/auth",
"build:app": "npm run build -w @tet/api -w @tet/ui -w @tet/app",
"build:site": "npm run build -w @tet/api -w @tet/ui -w @tet/site",
"start": "if [ \"$SCALINGO_BUILD\" = \"site\" ]; then npm start -w @tet/site; else npm start -w @tet/app; fi"
Expand Down
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@
"license": "ISC",
"description": "",
"peerDependencies": {
"@supabase/supabase-js": "^2.39.0"
"@supabase/supabase-js": "^2.39.3"
}
}
2 changes: 2 additions & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './typeUtils';
export type {Database, Json} from './database.types';
export {unaccent} from './utils/unaccent';
export * from './utils/authTokens';
export * from './utils/pathUtils';
export * as Indicateurs from './indicateurs';
export * as CollectiviteEngagee from './collectiviteEngagees';
59 changes: 59 additions & 0 deletions packages/api/src/utils/authTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Fonctions pour enlever ou ajouter les cookies qui permettent de partager
* l'authentification entre les sous-domaines.

* Ref: https://github.com/orgs/supabase/discussions/5742#discussioncomment-4050444
*/
import {Session, SupabaseClient} from '@supabase/supabase-js';

export const ACCESS_TOKEN = 'tet-access-token';
export const REFRESH_TOKEN = 'tet-refresh-token';

/** Enlève les tokens */
const EXPIRED = new Date(0).toUTCString();
const formatExpiredToken = (name: string, domain: string) =>
`${name}=; Domain=${domain}; path=/; expires=${EXPIRED}; SameSite=Lax; secure`;
export const clearAuthTokens = (domain: string) => {
document.cookie = formatExpiredToken(ACCESS_TOKEN, domain);
document.cookie = formatExpiredToken(REFRESH_TOKEN, domain);
};

/** Crée les tokens à partir de la session */
const MAX_AGE = 60 * 60 * 24 * 365;
export const formatAuthToken = (
name: string,
value: string,
domain: string,
maxAge: number = MAX_AGE
) =>
`${name}=${value}; Domain=${domain}; path=/; max-age=${maxAge}; SameSite=Lax; secure`;

/** Crée et ajoute les tokens */
export const setAuthTokens = (session: Session, domain: string) => {
document.cookie = formatAuthToken(ACCESS_TOKEN, session.access_token, domain);
document.cookie = formatAuthToken(
REFRESH_TOKEN,
session.refresh_token,
domain
);
};

/** Restaure la session depuis les tokens */
export const restoreSessionFromAuthTokens = async (
supabase: SupabaseClient
) => {
// recherche les cookies
const cookies = document.cookie
.split(/\s*;\s*/)
.map(cookie => cookie.split('='));
const accessTokenCookie = cookies.find(x => x[0] === ACCESS_TOKEN);
const refreshTokenCookie = cookies.find(x => x[0] === REFRESH_TOKEN);

if (accessTokenCookie && refreshTokenCookie) {
return supabase.auth.setSession({
access_token: accessTokenCookie[1],
refresh_token: refreshTokenCookie[1],
});
}
return null;
};
31 changes: 31 additions & 0 deletions packages/api/src/utils/pathUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {getAuthBaseUrl} from './pathUtils';
import {assert} from 'chai';

describe('getAuthBaseUrl', () => {
it("donne l'url de l'auth. en mode dev", () => {
assert.equal(getAuthBaseUrl('localhost'), 'http://localhost:3003');
});

it("donne l'url de l'auth. en mode preprod domaine tet", () => {
assert.equal(
getAuthBaseUrl('preprod-app.tet.fr'),
'https://preprod-auth.tet.fr'
);
});

it("donne l'url de l'auth. en mode preprod domaine koyeb", () => {
assert.equal(
getAuthBaseUrl('preprod-app-tet.koyeb.app'),
'https://preprod-auth-tet.koyeb.app'
);
assert.equal(
getAuthBaseUrl('test-app-branch-tet.koyeb.app'),
'https://preprod-auth-tet.koyeb.app'
);
});

it("donne l'url de l'auth. en mode prod domaine tet", () => {
assert.equal(getAuthBaseUrl('app.tet.fr'), 'https://auth.tet.fr');
assert.equal(getAuthBaseUrl('tet.fr'), 'https://auth.tet.fr');
});
});
66 changes: 66 additions & 0 deletions packages/api/src/utils/pathUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Extrait le domaine racine
* - transforme `app.territoiresentransitions.fr` en `territoiresentransitions.fr`
* - garde `territoiresentransitions.fr` inchangé
* - garde `localhost` inchangé
*/
export const getRootDomain = (hostname: string) => {
const parts = hostname.split('.');
return parts.length > 2 ? parts.toSpliced(0, 1).join('.') : hostname;
};

/**
* URL DES MODULES SUIVANT L'ENVIRONNEMENT
*
* DEV
* domain=localhost + port distinct (app=3000, site=3001, panier=3002,
* auth=3003)
*
* PREPROD dans le domaine TeT domain=territoiresentransitions.fr + sous-domaine
* distinct (preprod-app, preprod-site, preprod-panier, preprod-auth)
*
* PREPROD dans le domaine koyeb (pour permettre l'authent. depuis les app de
* test) domain=koyeb.app + sous-domaine distinct (preprod-app-tet,
* test-app-<branche>-tet, preprod-site-tet, preprod-panier-tet, preprod-auth-tet)
*
* PROD (domaine TeT) domain=territoiresentransitions.fr + sous-domaine distinct
* (app, panier, auth) ou pas de sous-domaine (ou wwww) pour le
* site
*/
/** Donne l'URL du module d'authentification */
const DEV_SITE_PORT = 3003; // port pour le mode dev
export const getAuthBaseUrl = (hostname: string) => {
const domain = getRootDomain(hostname);
if (domain === 'localhost') {
return `http://localhost:${DEV_SITE_PORT}`;
}

const subdomain =
hostname.includes('preprod') || domain === 'koyeb.app'
? domain === 'koyeb.app'
? 'preprod-auth-tet'
: 'preprod-auth'
: 'auth';
return `https://${subdomain}.${domain}`;
};

/**
* Donne les URLs des pages d'authentification
* @param hostname URL de la page courante pour pouvoir déterminer les chemins.
* @param redirect_to URL de la page vers laquelle rediriger après l'authentification.
* @returns
*/
export const getAuthPaths = (hostname: string, redirect_to: string) => {
const base = getAuthBaseUrl(hostname);
const params = new URLSearchParams({redirect_to});
return {
base,
login: `${base}/login?${params}`,
signUp: `${base}/signup?${params}`,
resetPwd: `${base}/recover?${params}`,
};
};

/** Donne l'url de la page "rejoindre une collectivité" */
export const getRejoindreCollectivitePath = (hostname: string) =>
`${getAuthBaseUrl(hostname)}/rejoindre-une-collectivite`;
2 changes: 1 addition & 1 deletion packages/api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2021",
"target": "esnext",
"sourceMap": true,
"declaration": true,
"declarationMap": true,
Expand Down
2 changes: 2 additions & 0 deletions packages/auth/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
NEXT_PUBLIC_SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
14 changes: 14 additions & 0 deletions packages/auth/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
extends: ['next/core-web-vitals'],
plugins: ['jsx-expressions'],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
rules: {
'jsx-expressions/strict-logical-expressions': 'warn',
'react/no-unescaped-entities': 'off',
},
};
38 changes: 38 additions & 0 deletions packages/auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

certificates
6 changes: 6 additions & 0 deletions packages/auth/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
bracketSpacing: false,
singleQuote: true,
arrowParens: 'avoid',
endOfLine: 'auto',
};
58 changes: 58 additions & 0 deletions packages/auth/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Configuration générale du storybook
*/

const path = require('path');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

module.exports = {
// pattern de recherche des stories
stories: ['../**/*.stories.tsx'],

// extensions chargées
addons: [
// les extensions les plus utilisées (Actions, Controls, Docs...)
'@storybook/addon-essentials',
// pour faire fonctionner tailwind dans le storybook
{
name: '@storybook/addon-styling-webpack',
options: {
rules: [
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {importLoaders: 1},
},
require.resolve('postcss-loader'),
],
},
],
},
},
],

// ajoute les alias de chemins d'import définis dans la config TS
webpackFinal: config => {
config.resolve.plugins = config.resolve.plugins || [];
config.resolve.plugins.push(
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, '../tsconfig.json'),
}),
);

return config;
},

framework: '@storybook/nextjs',

docs: {
autodocs: true,
},
core: {
builder: 'webpack5',
disableTelemetry: true,
},
};
4 changes: 4 additions & 0 deletions packages/auth/.storybook/preview.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** retire le souligné dsfr sous les liens dans la doc générée par le sb */
.sbdocs [href] {
background-image: none;
}
5 changes: 5 additions & 0 deletions packages/auth/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// charge les styles globaux
import '../app/global.css';

// surcharge les styles pour la zone de prévisualisation
import './preview.css';
36 changes: 36 additions & 0 deletions packages/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
15 changes: 15 additions & 0 deletions packages/auth/app/error/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client';

import {Alert} from '@tet/ui';

const ErrorPage = () => {
return (
<Alert
description="Une erreur est survenue. Veuillez contacter le support"
title="Erreur"
state="error"
/>
);
};

export default ErrorPage;
2 changes: 2 additions & 0 deletions packages/auth/app/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* feuille de styles globale fournie par le module partagé */
@import '@tet/ui/dist/global.css';
Loading
Loading