refactor: session and user

This commit is contained in:
cha0s 2024-01-13 14:27:48 -06:00
parent 00f1654ed9
commit 61f1a5b1f2
49 changed files with 969 additions and 434 deletions

View File

@ -9,11 +9,15 @@
'@flecks/electron': {}
'@flecks/fleck': {}
'@flecks/governor': {}
'@flecks/passport-local-react': {}
'@flecks/passport-local': {}
'@flecks/passport-react': {}
'@flecks/passport': {}
'@flecks/react': {}
'@flecks/redis': {}
'@flecks/redux': {}
'@flecks/repl': {}
'@flecks/server': {}
'@flecks/session': {}
'@flecks/socket': {}
'@flecks/user': {}
'@flecks/web': {}

View File

@ -25,8 +25,12 @@
"@flecks/redux": "*",
"@flecks/repl": "*",
"@flecks/server": "*",
"@flecks/session": "*",
"@flecks/socket": "*",
"@flecks/user": "*",
"@flecks/passport": "*",
"@flecks/passport-local": "*",
"@flecks/passport-local-react": "*",
"@flecks/passport-react": "*",
"@flecks/web": "*"
},
"dependencies": {

View File

@ -114,3 +114,6 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# copy-webpack-plugin charity
.git

View File

@ -0,0 +1,3 @@
module.exports = {
dependencies: ['@flecks/passport-local', '@flecks/passport-react'],
};

View File

@ -1,3 +1,3 @@
'@flecks/core': {}
'@flecks/react': {}
'@flecks/fleck': {}
'@flecks/react': {}

View File

@ -0,0 +1,30 @@
{
"name": "@flecks/passport-local-react",
"repository": {
"type": "git",
"url": "https://github.com/cha0s/flecks.git",
"directory": "packages/passport-local-react"
},
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"scripts": {
"build": "flecks build",
"clean": "flecks clean",
"lint": "flecks lint",
"postversion": "cp package.json dist",
"test": "flecks test"
},
"files": [
"index.js"
],
"dependencies": {
"@flecks/core": "^2.0.0",
"@flecks/passport-local": "^2.0.0",
"@flecks/react": "^2.0.0"
},
"devDependencies": {
"@flecks/fleck": "^2.0.0"
}
}

View File

@ -0,0 +1,9 @@
import {React} from '@flecks/react';
import PassportLocalLogin from './login';
export const hooks = {
'@flecks/passport-react.strategies': () => ({
Email: React.createElement(PassportLocalLogin),
}),
};

View File

@ -1,7 +1,7 @@
/* eslint-disable jsx-a11y/control-has-associated-label */
import {React} from '@flecks/react';
function UserLocalLogin() {
function PassportLocalLogin() {
return (
<form action="/auth/local" method="post">
<label>
@ -17,13 +17,11 @@ function UserLocalLogin() {
<input type="checkbox" />
<span>Remember me</span>
</label>
<a href="/auth/forgot">Forgot password?</a>
<a href="/auth/local/forgot">Forgot password?</a>
</div>
<label>
<input type="submit" value="Sign in" />
</label>
<input type="submit" value="Sign in" />
</form>
);
}
export default UserLocalLogin;
export default PassportLocalLogin;

119
packages/passport-local/.gitignore vendored Normal file
View File

@ -0,0 +1,119 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# copy-webpack-plugin charity
.git

View File

@ -0,0 +1,3 @@
module.exports = {
dependencies: ['@flecks/passport'],
};

View File

@ -0,0 +1,31 @@
{
"name": "@flecks/passport-local",
"repository": {
"type": "git",
"url": "https://github.com/cha0s/flecks.git",
"directory": "packages/passport-local"
},
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"scripts": {
"build": "flecks build",
"clean": "flecks clean",
"lint": "flecks lint",
"postversion": "cp package.json dist",
"test": "flecks test"
},
"files": [
"server.js"
],
"dependencies": {
"@flecks/core": "^2.0.3",
"@flecks/passport": "^2.0.3",
"bcrypt": "^5.1.1",
"passport-local": "^1.0.0"
},
"devDependencies": {
"@flecks/fleck": "^2.0.3"
}
}

View File

@ -0,0 +1,45 @@
import {randomBytes} from 'crypto';
import {Flecks} from '@flecks/core';
import LocalStrategy from 'passport-local';
export const hooks = {
'@flecks/db/server.models.decorate': Flecks.decorate(require.context('./models/decorators')),
'@flecks/passport.strategies': (passport, flecks) => ({
local: new LocalStrategy(
{usernameField: 'email'},
async (email, password, fn) => {
const {User} = flecks.db.Models;
try {
const user = await User.findOne({where: {email}});
fn(undefined, user && await user.validatePassword(password) && user);
}
catch (error) {
fn(error);
}
},
),
}),
'@flecks/repl.commands': (flecks) => {
const {User} = flecks.db.Models;
return {
createUser: async (spec) => {
const [email, maybePassword] = spec.split(' ', 2);
const password = maybePassword || randomBytes(8).toString('hex');
const user = User.build({email});
await user.addHashedPassword(password);
await user.save();
},
resetPassword: async (email) => {
const password = randomBytes(8).toString('hex');
const user = await User.findOne({where: {email}});
if (user) {
await user.addHashedPassword(password);
await user.save();
return `\nNew password: ${password}\n\n`;
}
return 'User not found.\n';
},
};
},
};

119
packages/passport-react/.gitignore vendored Normal file
View File

@ -0,0 +1,119 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# copy-webpack-plugin charity
.git

View File

@ -0,0 +1,3 @@
module.exports = {
dependencies: ['@flecks/passport', '@flecks/react'],
};

View File

@ -0,0 +1,3 @@
'@flecks/core': {}
'@flecks/fleck': {}
'@flecks/react': {}

View File

@ -0,0 +1,31 @@
{
"name": "@flecks/passport-react",
"repository": {
"type": "git",
"url": "https://github.com/cha0s/flecks.git",
"directory": "packages/passport-react"
},
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"scripts": {
"build": "flecks build",
"clean": "flecks clean",
"lint": "flecks lint",
"postversion": "cp package.json dist",
"test": "flecks test"
},
"files": [
"index.js"
],
"dependencies": {
"@flecks/core": "^2.0.0",
"@flecks/passport": "^2.0.0",
"@flecks/react": "^2.0.0",
"@flecks/redux": "^2.0.0"
},
"devDependencies": {
"@flecks/fleck": "^2.0.0"
}
}

View File

@ -0,0 +1,2 @@
export {default as Login} from './login';
export {default as UserRequired} from './user-required';

View File

@ -0,0 +1,24 @@
import {React, useFlecks} from '@flecks/react';
import {
Tab,
Tabs,
TabList,
TabPanel,
} from '@flecks/react/tabs';
function Login() {
const flecks = useFlecks();
const strategies = Object.entries(flecks.invokeMergeUnique('@flecks/passport-react.strategies'));
return (
<Tabs>
<TabList>
{strategies.map(([tab]) => <Tab key={tab}>{tab}</Tab>)}
</TabList>
<div className="behaved__tab-panels">
{strategies.map(([tab, panel]) => <TabPanel key={tab}>{panel}</TabPanel>)}
</div>
</Tabs>
);
}
export default Login;

View File

@ -1,7 +1,7 @@
import {PropTypes, React} from '@flecks/react';
import {Navigate, useLocation} from '@flecks/react/router';
import {useSelector} from '@flecks/redux';
import {userIdSelector} from '@flecks/user/state';
import {userIdSelector} from '@flecks/passport';
function UserRequired({children, destination}) {
const location = useLocation();

119
packages/passport/.gitignore vendored Normal file
View File

@ -0,0 +1,119 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# copy-webpack-plugin charity
.git

View File

@ -0,0 +1,3 @@
module.exports = {
dependencies: ['@flecks/db', '@flecks/session'],
};

View File

@ -1,15 +1,14 @@
{
"name": "@flecks/user",
"name": "@flecks/passport",
"repository": {
"type": "git",
"url": "https://github.com/cha0s/flecks.git",
"directory": "packages/user"
"directory": "packages/passport"
},
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"main": "index.js",
"scripts": {
"build": "flecks build",
"clean": "flecks clean",
@ -18,25 +17,17 @@
"test": "flecks test"
},
"files": [
"client.js",
"index.js",
"local.js",
"local/server.js",
"server.js",
"session/server.js"
"server.js"
],
"dependencies": {
"@flecks/core": "^2.0.3",
"@flecks/react": "^2.0.3",
"@flecks/db": "^2.0.3",
"@flecks/redux": "^2.0.3",
"bcrypt": "^5.0.0",
"express": "^4.17.1",
"express-session": "1.17.1",
"passport": "0.4.1",
"passport-local": "^1.0.0"
"@flecks/session": "^2.0.3",
"passport": "^0.7.0"
},
"devDependencies": {
"@babel/preset-react": "^7.12.10",
"@flecks/fleck": "^2.0.3"
}
}

View File

@ -0,0 +1,10 @@
import {Flecks} from '@flecks/core';
import {user, users} from './state';
export * from './state';
export const hooks = {
'@flecks/redux.slices': () => ({user, users}),
'@flecks/socket.packets': Flecks.provide(require.context('./packets')),
};

View File

@ -0,0 +1,4 @@
export default (flecks) => {
const {Packet} = flecks.fleck('@flecks/socket');
return class Logout extends Packet {};
};

View File

@ -0,0 +1,143 @@
import {D, Flecks} from '@flecks/core';
import passport from 'passport';
import LogOps from 'passport/lib/http/request';
const debug = D('@flecks/passport');
const debugSilly = debug.extend('silly');
export const hooks = {
'@flecks/core.config': () => ({
/**
* Path to redirect to after failed login.
*/
failureRedirect: '/',
/**
* Path to redirect to after logout.
*/
logoutRedirect: '/',
/**
* Path to redirect to after successful login.
*/
successRedirect: '/',
}),
'@flecks/core.mixin': (Flecks) => (
class FlecksWithSession extends Flecks {
passport;
}
),
'@flecks/db/server.models': Flecks.provide(require.context('./models')),
'@flecks/socket.packets.decorate': Flecks.decorate(require.context('./packets/decorators')),
'@flecks/web/server.request.route': Flecks.priority(
(flecks) => (req, res, next) => {
flecks.passport.initialize(req, res, () => {
debugSilly('@flecks/web/server.request.route: passport.session()');
flecks.passport.session(req, res, () => {
if (!req.user) {
const {User} = flecks.db.Models;
req.user = new User();
req.user.id = 0;
}
else {
debugSilly('web user ID: %s', req.user.id);
}
next();
});
});
},
{after: '@flecks/session/server'},
),
'@flecks/server.up': Flecks.priority(
async (flecks) => {
passport.serializeUser((user, fn) => fn(null, user.id));
passport.deserializeUser(async (id, fn) => {
const {User} = flecks.db.Models;
try {
fn(undefined, await User.findByPk(id));
}
catch (error) {
fn(error);
}
});
flecks.passport = {
initialize: passport.initialize(),
session: passport.session(),
strategies: flecks.invokeMergeUnique('@flecks/passport.strategies', passport),
};
Object.entries(flecks.passport.strategies)
.forEach(([name, strategy]) => {
passport.use(name, strategy);
});
},
{before: '@flecks/web/server', after: ['@flecks/db/server', '@flecks/session/server']},
),
'@flecks/socket.intercom': () => ({
'@flecks/passport.users': async (sids, server) => {
const sockets = await server.sockets();
return sids
.filter((sid) => sockets.has(sid))
.reduce(
(r, sid) => ({
...r,
[sid]: sockets.get(sid).handshake.user.id,
}),
{},
);
},
}),
'@flecks/socket/server.request.socket': Flecks.priority(
(flecks) => (socket, next) => {
const {req} = socket;
flecks.passport.initialize(req, undefined, () => {
flecks.passport.session(req, undefined, async () => {
if (!req.user) {
const {User} = flecks.db.Models;
req.user = new User();
req.user.id = 0;
}
else {
debugSilly('socket user ID: %s', req.user.id);
}
req.login = LogOps.logIn;
req.logIn = LogOps.logIn;
req.logout = LogOps.logOut;
req.logOut = LogOps.logOut;
req.isAuthenticated = LogOps.isAuthenticated;
req.isUnauthenticated = LogOps.isUnauthenticated;
await socket.join(`/u/${req.user.id}`);
next();
});
});
},
{after: '@flecks/session/server', before: '@flecks/governor/server'},
),
'@flecks/web.routes': (flecks) => {
const {
failureRedirect,
logoutRedirect,
successRedirect,
} = flecks.get('@flecks/passport/server');
const routes = [
{
method: 'get',
path: '/auth/logout',
middleware: (req, res) => {
req.logout();
res.redirect(logoutRedirect);
},
},
];
Object.entries(flecks.passport.strategies)
.forEach(([name]) => {
routes.push(
{
method: 'post',
path: `/auth/${name}`,
middleware: passport.authenticate(name, {failureRedirect, successRedirect}),
},
);
});
return routes;
},
};

View File

@ -0,0 +1,21 @@
export default (Logout, flecks) => {
const {ValidationError} = flecks.fleck('@flecks/socket');
return class ServerLogout extends Logout {
static async respond(packet, socket) {
const {req} = socket;
req.logout();
await new Promise((r, e) => {
req.session.save((error) => (error ? e(error) : r()));
});
socket.send(['Redirect', flecks.get('@flecks/passport/server.logoutRedirect')]);
}
static validate(packet, {req}) {
if (!req.user) {
throw new ValidationError({code: 400, reason: 'anonymous'});
}
}
};
};

View File

@ -14,7 +14,7 @@ export const userSelector = createSelector(
);
const slice = createSlice({
name: '@flecks/user.user',
name: '@flecks/passport.user',
initialState: 0,
extraReducers: (builder) => {
builder.addCase(hydrateServer, (state, action) => {

View File

@ -15,7 +15,7 @@ export const {
} = adapter.getSelectors((state) => state.users);
const slice = createSlice({
name: '@flecks/user.users',
name: '@flecks/passport.users',
initialState: adapter.getInitialState({
entities: {0: {name: 'anonymous'}},
ids: [0],

View File

@ -13,10 +13,10 @@ const RedisStore = ConnectRedis(session);
export const hooks = {
'@flecks/core.priority': (graph, hook) => {
if ('@flecks/server.up' === hook) {
graph.addDependency('@flecks/user/session/server', '@flecks/redis/server');
graph.addDependency('@flecks/session/server', '@flecks/redis/server');
}
},
'@flecks/user.session': async (flecks) => {
'@flecks/session.config': async (flecks) => {
const client = createClient(flecks, {legacyMode: true});
await client.connect();
return {

119
packages/session/.gitignore vendored Normal file
View File

@ -0,0 +1,119 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# copy-webpack-plugin charity
.git

View File

@ -0,0 +1,30 @@
{
"name": "@flecks/session",
"repository": {
"type": "git",
"url": "https://github.com/cha0s/flecks.git",
"directory": "packages/session"
},
"publishConfig": {
"access": "public"
},
"version": "2.0.3",
"scripts": {
"build": "flecks build",
"clean": "flecks clean",
"lint": "flecks lint",
"postversion": "cp package.json dist",
"test": "flecks test"
},
"files": [
"server.js"
],
"dependencies": {
"@flecks/core": "^2.0.0",
"express": "^4.18.2",
"express-session": "^1.17.3"
},
"devDependencies": {
"@flecks/fleck": "^2.0.0"
}
}

View File

@ -0,0 +1,62 @@
import {D} from '@flecks/core';
import express from 'express';
import expressSession from 'express-session';
const debug = D('@flecks/session');
const debugSilly = debug.extend('silly');
export const hooks = {
'@flecks/core.config': () => ({
/**
* Set the cookie secret for session encryption.
*
* See: http://expressjs.com/en/resources/middleware/cookie-parser.html
*/
cookieSecret: (
'Set the FLECKS_ENV__flecks_session_server__cookieSecret environment variable!'
),
}),
'@flecks/core.mixin': (Flecks) => (
class FlecksWithSession extends Flecks {
session;
}
),
'@flecks/web/server.request.route': (flecks) => {
const urle = express.urlencoded({extended: true});
return (req, res, next) => {
urle(req, res, (error) => {
if (error) {
next(error);
return;
}
flecks.session(req, res, (error) => {
if (error) {
next(error);
return;
}
debugSilly('web session ID: %s', req.session.id);
next();
});
});
};
},
'@flecks/server.up': async (flecks) => {
flecks.session = expressSession({
resave: false,
sameSite: true,
saveUninitialized: false,
secret: flecks.get('@flecks/session/server.cookieSecret'),
...await flecks.invokeMergeAsync('@flecks/session.config'),
});
},
'@flecks/socket/server.request.socket': (flecks) => (socket, next) => {
flecks.session(socket.handshake, {}, () => {
const id = socket.handshake.session?.id;
socket.join(id);
debugSilly('socket session ID: %s', id);
next();
});
},
};

View File

@ -1,115 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.0.3](https://github.com/cha0s/flecks/compare/v2.0.2...v2.0.3) (2023-11-22)
### Bug Fixes
* electron ([81fc978](https://github.com/cha0s/flecks/commit/81fc978da2b8d32e303d165fe7c2f6071ac8e741))
## [2.0.2](https://github.com/cha0s/flecks/compare/v2.0.1...v2.0.2) (2023-11-22)
**Note:** Version bump only for package @flecks/user
## [2.0.1](https://github.com/cha0s/flecks/compare/v2.0.0...v2.0.1) (2023-11-22)
**Note:** Version bump only for package @flecks/user
# [2.0.0](https://github.com/cha0s/flecks/compare/v1.4.1...v2.0.0) (2023-11-22)
### Features
* UserRequired ([9b5f6f4](https://github.com/cha0s/flecks/commit/9b5f6f4818afaec76ee941c8d2c4795912234ee8))
* webpack 5 support ([288b368](https://github.com/cha0s/flecks/commit/288b368b9ff96be5ccb58bd811838a4a4bb6c48c))
## [1.4.1](https://github.com/cha0s/flecks/compare/v1.4.0...v1.4.1) (2022-03-19)
**Note:** Version bump only for package @flecks/user
# [1.4.0](https://github.com/cha0s/flecks/compare/v1.3.0...v1.4.0) (2022-03-19)
**Note:** Version bump only for package @flecks/user
# [1.3.0](https://github.com/cha0s/flecks/compare/v1.2.1...v1.3.0) (2022-03-09)
**Note:** Version bump only for package @flecks/user
## [1.2.1](https://github.com/cha0s/flecks/compare/v1.2.0...v1.2.1) (2022-03-08)
**Note:** Version bump only for package @flecks/user
# [1.2.0](https://github.com/cha0s/flecks/compare/v1.1.1...v1.2.0) (2022-03-07)
### Bug Fixes
* **user:** redux hydration ([60ab421](https://github.com/cha0s/flecks/commit/60ab4219887daa2efc12ca26e02c76287a4fd779))
### Features
* docs ([5e2b825](https://github.com/cha0s/flecks/commit/5e2b8256205efc0280c8308cea3e64fa06fe16a9))
## [1.1.1](https://github.com/cha0s/flecks/compare/v1.1.0...v1.1.1) (2022-02-28)
**Note:** Version bump only for package @flecks/user
# [1.1.0](https://github.com/cha0s/flecks/compare/v1.0.2...v1.1.0) (2022-02-28)
### Features
* babel in flecksrc ([dc60217](https://github.com/cha0s/flecks/commits/dc60217bd66d436eac6afe0e6d803f43a354bc6b))
* rich debugging ([545bfce](https://github.com/cha0s/flecks/commits/545bfce1ab602044041b370b413df62ae0cb9363))
## [1.0.2](https://github.com/cha0s/flecks/compare/v1.0.1...v1.0.2) (2022-02-28)
**Note:** Version bump only for package @flecks/user

View File

@ -1,10 +0,0 @@
export const hooks = {
/**
* Modify express-session configuration.
*
* See: https://www.npmjs.com/package/express-session
*/
'@flecks/user.session': () => ({
saveUninitialized: true,
}),
};

View File

@ -1,15 +0,0 @@
import {Logout} from './packets';
import {user, users} from './state';
export * from './state';
export const hooks = {
'@flecks/redux.slices': () => ({
user,
users,
}),
'@flecks/socket.packets': (flecks) => ({
Logout: Logout(flecks),
}),
};

View File

@ -1,2 +0,0 @@
export {default as UserLocalLogin} from './user-local-login';
export {default as UserRequired} from './user-required';

View File

@ -1 +0,0 @@
export * from './components';

View File

@ -1,71 +0,0 @@
import {randomBytes} from 'crypto';
import {Flecks} from '@flecks/core';
import passport from 'passport';
import LocalStrategy from 'passport-local';
export const hooks = {
'@flecks/core.config': () => ({
/**
* Path to redirect to after failed login.
*/
failureRedirect: '/',
/**
* Path to redirect to after successful login.
*/
successRedirect: '/',
}),
'@flecks/db/server.models.decorate': (
Flecks.decorate(require.context('./models/decorators', false, /\.js$/))
),
'@flecks/web.routes': (flecks) => {
const {failureRedirect, successRedirect} = flecks.get('@flecks/user/local/server');
return [
{
method: 'post',
path: '/auth/local',
middleware: passport.authenticate('local', {failureRedirect, successRedirect}),
},
];
},
'@flecks/repl.commands': (flecks) => {
const {User} = flecks.db.Models;
return {
createUser: async (spec) => {
const [email, maybePassword] = spec.split(' ', 2);
const password = maybePassword || randomBytes(8).toString('hex');
const user = User.build({email});
await user.addHashedPassword(password);
await user.save();
},
resetPassword: async (email) => {
const password = randomBytes(8).toString('hex');
const user = await User.findOne({where: {email}});
if (user) {
await user.addHashedPassword(password);
await user.save();
return `\nNew password: ${password}\n\n`;
}
return 'User not found.\n';
},
};
},
'@flecks/server.up': Flecks.priority(
(flecks) => {
passport.use(new LocalStrategy(
{usernameField: 'email'},
async (email, password, fn) => {
const {User} = flecks.db.Models;
try {
const user = await User.findOne({where: {email}});
fn(undefined, user && await user.validatePassword(password) && user);
}
catch (error) {
fn(error);
}
},
));
},
{after: '@flecks/user/server', before: '@flecks/repl/server'},
),
};

View File

@ -1,2 +0,0 @@
// eslint-disable-next-line import/prefer-default-export
export {default as Logout} from './logout';

View File

@ -1,20 +0,0 @@
export default (flecks) => {
const {Packet, ValidationError} = flecks.fleck('@flecks/socket');
return class Logout extends Packet {
static respond(packet, {req}) {
req.logout();
return new Promise((r, e) => {
req.session.save((error) => (error ? e(error) : r()));
});
}
static validate(packet, {req}) {
if (!req.user) {
throw new ValidationError({code: 400, reason: 'anonymous'});
}
}
};
};

View File

@ -1,87 +0,0 @@
import {D, Flecks} from '@flecks/core';
import passport from 'passport';
import LogOps from 'passport/lib/http/request';
const debug = D('@flecks/user/passport');
const debugSilly = debug.extend('silly');
export const hooks = {
'@flecks/db/server.models': Flecks.provide(require.context('./models', false, /\.js$/)),
'@flecks/web/server.request.route': (flecks) => (req, res, next) => {
debugSilly('@flecks/web/server.request.route: passport.initialize()');
passport.initialize()(req, res, () => {
debugSilly('@flecks/web/server.request.route: passport.session()');
passport.session()(req, res, () => {
if (!req.user) {
const {User} = flecks.db.Models;
req.user = new User();
req.user.id = 0;
}
next();
});
});
},
'@flecks/web.routes': () => [
{
method: 'get',
path: '/auth/logout',
middleware: (req, res) => {
req.logout();
res.redirect('/');
},
},
],
'@flecks/server.up': Flecks.priority(
(flecks) => {
passport.serializeUser((user, fn) => fn(null, user.id));
passport.deserializeUser(async (id, fn) => {
const {User} = flecks.db.Models;
try {
fn(undefined, await User.findByPk(id));
}
catch (error) {
fn(error);
}
});
},
{before: '@flecks/web/server', after: ['@flecks/db/server', '@flecks/governor/server']},
),
'@flecks/socket.intercom': () => ({
'@flecks/user.users': async (sids, server) => {
const sockets = await server.sockets();
return sids
.filter((sid) => sockets.has(sid))
.reduce(
(r, sid) => ({
...r,
[sid]: sockets.get(sid).handshake.user.id,
}),
{},
);
},
}),
'@flecks/socket/server.request.socket': Flecks.priority(
(flecks) => (socket, next) => {
debugSilly('@flecks/socket/server.request.socket: passport.initialize()');
passport.initialize()(socket.handshake, undefined, () => {
debugSilly('@flecks/socket/server.request.socket: passport.session()');
passport.session()(socket.handshake, undefined, async () => {
if (!socket.handshake.user) {
const {User} = flecks.db.Models;
socket.handshake.user = new User();
socket.handshake.user.id = 0;
}
socket.handshake.login = LogOps.logIn;
socket.handshake.logIn = LogOps.logIn;
socket.handshake.logout = LogOps.logOut;
socket.handshake.logOut = LogOps.logOut;
socket.handshake.isAuthenticated = LogOps.isAuthenticated;
socket.handshake.isUnauthenticated = LogOps.isUnauthenticated;
await socket.join(`/u/${socket.handshake.user.id}`);
next();
});
});
},
{before: '@flecks/governor/server'},
),
};

View File

@ -1,75 +0,0 @@
import {D, Flecks} from '@flecks/core';
import express from 'express';
import expressSession from 'express-session';
const debug = D('@flecks/user/session');
const debugSilly = debug.extend('silly');
export const hooks = {
'@flecks/core.config': () => ({
/**
* Set the cookie secret for session encryption.
*
* See: http://expressjs.com/en/resources/middleware/cookie-parser.html
*/
cookieSecret: (
'Set the FLECKS_ENV__flecks_user_session_server__cookieSecret environment variable!'
),
}),
'@flecks/core.mixin': (Flecks) => (
class FlecksWithUser extends Flecks {
user = {
session: undefined,
}
}
),
'@flecks/web/server.request.route': Flecks.priority(
(flecks) => {
const urle = express.urlencoded({extended: true});
return (req, res, next) => {
debugSilly('@flecks/web/server.request.route: express.urlencoded()');
urle(req, res, (error) => {
if (error) {
next(error);
return;
}
debugSilly('@flecks/web/server.request.route: session()');
flecks.user.session(req, res, (error) => {
if (error) {
next(error);
return;
}
debugSilly('session ID: %s', req.session.id);
next();
});
});
};
},
{before: '@flecks/user/server'},
),
'@flecks/server.up': Flecks.priority(
async (flecks) => {
flecks.user.session = expressSession({
resave: false,
sameSite: true,
saveUninitialized: false,
secret: flecks.get('@flecks/user/session/server.cookieSecret'),
...await flecks.invokeMergeAsync('@flecks/user.session'),
});
},
{after: ['@flecks/governor/server', '@flecks/user/server']},
),
'@flecks/socket/server.request.socket': Flecks.priority(
(flecks) => (socket, next) => {
debugSilly('@flecks/socket/server.request.socket: session()');
flecks.user.session(socket.handshake, {}, () => {
const id = socket.handshake.session?.id;
socket.join(id);
next();
});
},
{before: '@flecks/user/server'},
),
};

View File

@ -18,11 +18,11 @@ Our `flecks.yml` could be configured like so:
```yaml
'@flecks/http/server':
'request.route':
- '@flecks/user/session'
- '@flecks/session'
- '@my-app/dark-mode-check'
```
In this application, when `@flecks/http/server.request.route` is invoked, `@flecks/user/session`'s
In this application, when `@flecks/http/server.request.route` is invoked, `@flecks/session`'s
implementation is invoked (which reifies the user's session from cookies), followed by
`@my-app/dark-mode-check`'s.
@ -38,7 +38,7 @@ provides you with the ellipses entry:
```yaml
'@flecks/http/server':
'request.route':
- '@flecks/user/session'
- '@flecks/session'
- '...'
- '@my-app/finalize'
```
@ -71,7 +71,7 @@ export const hooks = {
}
```
Then this is the same implementation but configured to run after `@flecks/user/session/server`:
Then this is the same implementation but configured to run after `@flecks/session/server`:
```js
export const hooks = {
@ -81,7 +81,7 @@ export const hooks = {
// ...
}
},
{after: '@flecks/user/session/server'},
{after: '@flecks/session/server'},
),
}
```

View File

@ -227,7 +227,7 @@ const result = await socket.send(['Whatever']);
Whenever the client receives an `Action` packet, the action will be dispatched by
the redux store.
- `@flecks/user` implements a `validate` acceptor method for the `Logout` packet:
- `@flecks/passport` implements a `validate` acceptor method for the `Logout` packet:
```javascript
static validate(packet, {req}) {
@ -247,7 +247,7 @@ parameters:
### `type`
The type of intercom call to make (e.g. `@flecks/user.users`).
The type of intercom call to make (e.g. `@flecks/passport.users`).
### `data`