This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+10
View File
@@ -0,0 +1,10 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="@adonisjs/http-server/build/adonis-typings" />
import { ServerContract } from '@ioc:Adonis/Core/Server';
import { SessionManagerContract } from '@ioc:Adonis/Addons/Session';
import { HttpContextConstructorContract } from '@ioc:Adonis/Core/HttpContext';
/**
* Share "session" with the HTTP context. Define hooks to initiate and
* commit session when sessions are enabled.
*/
export declare function defineServerBindings(HttpContext: HttpContextConstructorContract, Server: ServerContract, Session: SessionManagerContract): void;
+42
View File
@@ -0,0 +1,42 @@
"use strict";
/*
* @adonisjs/session
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineServerBindings = void 0;
/**
* Share "session" with the HTTP context. Define hooks to initiate and
* commit session when sessions are enabled.
*/
function defineServerBindings(HttpContext, Server, Session) {
/**
* Sharing session with the context
*/
HttpContext.getter('session', function session() {
return Session.create(this);
}, true);
/**
* Do not register hooks when sessions are disabled
*/
if (!Session.isEnabled()) {
return;
}
/**
* Initiate session store
*/
Server.hooks.before(async (ctx) => {
await ctx.session.initiate(false);
});
/**
* Commit store mutations
*/
Server.hooks.after(async (ctx) => {
await ctx.session.commit();
});
}
exports.defineServerBindings = defineServerBindings;
+7
View File
@@ -0,0 +1,7 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { ContainerBindings } from '@ioc:Adonis/Core/Application';
import { SessionManagerContract } from '@ioc:Adonis/Addons/Session';
/**
* Define test bindings
*/
export declare function defineTestsBindings(ApiRequest: ContainerBindings['Japa/Preset/ApiRequest'], ApiResponse: ContainerBindings['Japa/Preset/ApiResponse'], ApiClient: ContainerBindings['Japa/Preset/ApiClient'], SessionManager: SessionManagerContract): void;
+140
View File
@@ -0,0 +1,140 @@
"use strict";
/*
* @adonisjs/session
*
* (c) AdonisJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.defineTestsBindings = void 0;
const util_1 = require("util");
/**
* Define test bindings
*/
function defineTestsBindings(ApiRequest, ApiResponse, ApiClient, SessionManager) {
/**
* Set "sessionClient" on the api request
*/
ApiRequest.getter('sessionClient', function () {
return SessionManager.client();
}, true);
/**
* Send session values in the request
*/
ApiRequest.macro('session', function (session) {
if (!this.sessionClient.isEnabled()) {
throw new Error('Cannot set session. Make sure to enable it inside "config/session" file');
}
this.sessionClient.merge(session);
return this;
});
/**
* Send flash messages in the request
*/
ApiRequest.macro('flashMessages', function (messages) {
if (!this.sessionClient.isEnabled()) {
throw new Error('Cannot set flash messages. Make sure to enable the session inside "config/session" file');
}
this.sessionClient.flashMessages.merge(messages);
return this;
});
/**
* Returns reference to the session data from the session
* jar
*/
ApiResponse.macro('session', function () {
return this.sessionJar.session;
});
/**
* Returns reference to the flash messages from the session
* jar
*/
ApiResponse.macro('flashMessages', function () {
return this.sessionJar.flashMessages || {};
});
/**
* Assert response to contain a given session and optionally
* has the expected value
*/
ApiResponse.macro('assertSession', function (name, value) {
this.ensureHasAssert();
this.assert.property(this.session(), name);
if (value !== undefined) {
this.assert.deepEqual(this.session()[name], value);
}
});
/**
* Assert response to not contain a given session
*/
ApiResponse.macro('assertSessionMissing', function (name) {
this.ensureHasAssert();
this.assert.notProperty(this.session(), name);
});
/**
* Assert response to contain a given flash message and optionally
* has the expected value
*/
ApiResponse.macro('assertFlashMessage', function (name, value) {
this.ensureHasAssert();
this.assert.property(this.flashMessages(), name);
if (value !== undefined) {
this.assert.deepEqual(this.flashMessages()[name], value);
}
});
/**
* Assert response to not contain a given session
*/
ApiResponse.macro('assertFlashMissing', function (name) {
this.ensureHasAssert();
this.assert.notProperty(this.flashMessages(), name);
});
/**
* Dump session to the console
*/
ApiResponse.macro('dumpSession', function (options) {
const inspectOptions = { depth: 2, showHidden: false, colors: true, ...options };
console.log(`"session" => ${(0, util_1.inspect)(this.session(), inspectOptions)}`);
console.log(`"flashMessages" => ${(0, util_1.inspect)(this.flashMessages(), inspectOptions)}`);
});
/**
* Adding hooks directly on the request object moves the hooks to
* the end of the queue (basically after the globally hooks)
*/
ApiClient.onRequest((req) => {
/**
* Hook into request and persist session data to be available
* on the server during the request.
*/
req.setup(async (request) => {
/**
* Persist session data and set the session id within the
* cookie
*/
const { cookieName, sessionId } = await request.sessionClient.commit();
request.cookie(cookieName, sessionId);
/**
* Cleanup if request has error. Otherwise the teardown
* hook will clear
*/
return async (error) => {
if (error) {
await request.sessionClient.forget();
}
};
});
/**
* Load messages from the session store and keep a reference to it
* inside the response object.
*
* We also destroy the session after getting a copy of the session
* data
*/
req.teardown(async (response) => {
response.sessionJar = await response.request.sessionClient.load(response.cookies());
await response.request.sessionClient.forget();
});
});
}
exports.defineTestsBindings = defineTestsBindings;
+55
View File
@@ -0,0 +1,55 @@
/// <reference path="../../adonis-typings/index.d.ts" />
/// <reference types="@adonisjs/http-server/build/adonis-typings" />
import { SessionConfig, SessionDriverContract, SessionClientContract } from '@ioc:Adonis/Addons/Session';
import { CookieClientContract } from '@ioc:Adonis/Core/CookieClient';
import { Store } from '../Store';
/**
* SessionClient exposes the API to set session data as a client
*/
export declare class SessionClient extends Store implements SessionClientContract {
private config;
private driver;
private cookieClient;
/**
* Each instance of client works on a single session id. Generate
* multiple client instances for a different session id
*/
private sessionId;
/**
* Session key for setting flash messages
*/
private flashMessagesKey;
/**
* Flash messages store. They are merged with the session data during
* commit
*/
flashMessages: Store;
constructor(config: SessionConfig, driver: SessionDriverContract, cookieClient: CookieClientContract, values: {
[key: string]: any;
} | null);
/**
* Find if the sessions are enabled
*/
isEnabled(): boolean;
/**
* Load session from the driver
*/
load(cookies: Record<string, any>): Promise<{
session: any;
flashMessages: any;
}>;
/**
* Commits the session data to the session store and returns
* the session id and cookie name for it to be accessible
* by the server
*/
commit(): Promise<{
sessionId: string;
signedSessionId: string;
cookieName: string;
}>;
/**
* Clear the session store
*/
forget(): Promise<void>;
}
+93
View File
@@ -0,0 +1,93 @@
"use strict";
/*
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionClient = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const helpers_1 = require("@poppinss/utils/build/helpers");
const Store_1 = require("../Store");
/**
* SessionClient exposes the API to set session data as a client
*/
class SessionClient extends Store_1.Store {
constructor(config, driver, cookieClient, values) {
super(values);
this.config = config;
this.driver = driver;
this.cookieClient = cookieClient;
/**
* Each instance of client works on a single session id. Generate
* multiple client instances for a different session id
*/
this.sessionId = (0, helpers_1.cuid)();
/**
* Session key for setting flash messages
*/
this.flashMessagesKey = '__flash__';
/**
* Flash messages store. They are merged with the session data during
* commit
*/
this.flashMessages = new Store_1.Store({});
}
/**
* Find if the sessions are enabled
*/
isEnabled() {
return this.config.enabled;
}
/**
* Load session from the driver
*/
async load(cookies) {
const sessionIdCookie = cookies[this.config.cookieName];
const sessionId = sessionIdCookie ? sessionIdCookie.value : this.sessionId;
const contents = await this.driver.read(sessionId);
const store = new Store_1.Store(contents);
const flashMessages = store.pull(this.flashMessagesKey, null);
return {
session: store.all(),
flashMessages,
};
}
/**
* Commits the session data to the session store and returns
* the session id and cookie name for it to be accessible
* by the server
*/
async commit() {
this.set(this.flashMessagesKey, this.flashMessages.all());
await this.driver.write(this.sessionId, this.toJSON());
/**
* Clear from the session client memory
*/
this.clear();
this.flashMessages.clear();
return {
sessionId: this.sessionId,
signedSessionId: this.cookieClient.sign(this.config.cookieName, this.sessionId),
cookieName: this.config.cookieName,
};
}
/**
* Clear the session store
*/
async forget() {
/**
* Clear from the session client memory
*/
this.clear();
this.flashMessages.clear();
/**
* Clear with the driver
*/
await this.driver.destroy(this.sessionId);
}
}
exports.SessionClient = SessionClient;
+31
View File
@@ -0,0 +1,31 @@
/// <reference path="../../adonis-typings/session.d.ts" />
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { SessionDriverContract, SessionConfig } from '@ioc:Adonis/Addons/Session';
/**
* Cookie driver utilizes the encrypted HTTP cookies to write session value.
*/
export declare class CookieDriver implements SessionDriverContract {
private config;
private ctx;
constructor(config: SessionConfig, ctx: HttpContextContract);
/**
* Read session value from the cookie
*/
read(sessionId: string): {
[key: string]: any;
} | null;
/**
* Write session values to the cookie
*/
write(sessionId: string, values: {
[key: string]: any;
}): void;
/**
* Removes the session cookie
*/
destroy(sessionId: string): void;
/**
* Updates the cookie with existing cookie values
*/
touch(sessionId: string): void;
}
+58
View File
@@ -0,0 +1,58 @@
"use strict";
/*
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CookieDriver = void 0;
/**
* Cookie driver utilizes the encrypted HTTP cookies to write session value.
*/
class CookieDriver {
constructor(config, ctx) {
this.config = config;
this.ctx = ctx;
}
/**
* Read session value from the cookie
*/
read(sessionId) {
const cookieValue = this.ctx.request.encryptedCookie(sessionId);
if (typeof cookieValue !== 'object') {
return null;
}
return cookieValue;
}
/**
* Write session values to the cookie
*/
write(sessionId, values) {
if (typeof values !== 'object') {
throw new Error('Session cookie driver expects an object of values');
}
this.ctx.response.encryptedCookie(sessionId, values, this.config.cookie);
}
/**
* Removes the session cookie
*/
destroy(sessionId) {
if (this.ctx.request.cookiesList()[sessionId]) {
this.ctx.response.clearCookie(sessionId);
}
}
/**
* Updates the cookie with existing cookie values
*/
touch(sessionId) {
const value = this.read(sessionId);
if (!value) {
return;
}
this.write(sessionId, value);
}
}
exports.CookieDriver = CookieDriver;
+42
View File
@@ -0,0 +1,42 @@
/**
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { SessionDriverContract, SessionConfig } from '@ioc:Adonis/Addons/Session';
/**
* File driver to read/write session to filesystem
*/
export declare class FileDriver implements SessionDriverContract {
private config;
constructor(config: SessionConfig);
/**
* Returns complete path to the session file
*/
private getFilePath;
/**
* Returns file contents. A new file will be created if it's
* missing.
*/
read(sessionId: string): Promise<{
[key: string]: any;
} | null>;
/**
* Write session values to a file
*/
write(sessionId: string, values: {
[key: string]: any;
}): Promise<void>;
/**
* Cleanup session file by removing it
*/
destroy(sessionId: string): Promise<void>;
/**
* Writes the value by reading it from the store
*/
touch(sessionId: string): Promise<void>;
}
+80
View File
@@ -0,0 +1,80 @@
"use strict";
/**
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileDriver = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const path_1 = require("path");
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const fs_extra_1 = require("fs-extra");
/**
* File driver to read/write session to filesystem
*/
class FileDriver {
constructor(config) {
this.config = config;
if (!this.config.file || !this.config.file.location) {
throw new utils_1.Exception('Missing "file.location" for session file driver inside "config/session" file', 500, 'E_INVALID_SESSION_DRIVER_CONFIG');
}
}
/**
* Returns complete path to the session file
*/
getFilePath(sessionId) {
return (0, path_1.join)(this.config.file.location, `${sessionId}.txt`);
}
/**
* Returns file contents. A new file will be created if it's
* missing.
*/
async read(sessionId) {
const filePath = this.getFilePath(sessionId);
await (0, fs_extra_1.ensureFile)(filePath);
const contents = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
if (!contents.trim()) {
return null;
}
/**
* Verify contents with the session id and return them as an object.
*/
const verifiedContents = new helpers_1.MessageBuilder().verify(contents.trim(), sessionId);
if (typeof verifiedContents !== 'object') {
return null;
}
return verifiedContents;
}
/**
* Write session values to a file
*/
async write(sessionId, values) {
if (typeof values !== 'object') {
throw new Error('Session file driver expects an object of values');
}
const message = new helpers_1.MessageBuilder().build(values, undefined, sessionId);
await (0, fs_extra_1.outputFile)(this.getFilePath(sessionId), message);
}
/**
* Cleanup session file by removing it
*/
async destroy(sessionId) {
await (0, fs_extra_1.remove)(this.getFilePath(sessionId));
}
/**
* Writes the value by reading it from the store
*/
async touch(sessionId) {
const value = await this.read(sessionId);
if (!value) {
return;
}
await this.write(sessionId, value);
}
}
exports.FileDriver = FileDriver;
+33
View File
@@ -0,0 +1,33 @@
/**
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { SessionDriverContract } from '@ioc:Adonis/Addons/Session';
/**
* Memory driver is meant to be used for writing tests.
*/
export declare class MemoryDriver implements SessionDriverContract {
static sessions: Map<string, Object>;
/**
* Read session id value from the memory
*/
read(sessionId: string): {
[key: string]: any;
} | null;
/**
* Save in memory value for a given session id
*/
write(sessionId: string, values: {
[key: string]: any;
}): void;
/**
* Cleanup for a single session
*/
destroy(sessionId: string): void;
touch(): void;
}
+40
View File
@@ -0,0 +1,40 @@
"use strict";
/**
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryDriver = void 0;
/**
* Memory driver is meant to be used for writing tests.
*/
class MemoryDriver {
/**
* Read session id value from the memory
*/
read(sessionId) {
return MemoryDriver.sessions.get(sessionId) || null;
}
/**
* Save in memory value for a given session id
*/
write(sessionId, values) {
if (typeof values !== 'object') {
throw new Error('Session memory driver expects an object of values');
}
MemoryDriver.sessions.set(sessionId, values);
}
/**
* Cleanup for a single session
*/
destroy(sessionId) {
MemoryDriver.sessions.delete(sessionId);
}
touch() { }
}
exports.MemoryDriver = MemoryDriver;
MemoryDriver.sessions = new Map();
+46
View File
@@ -0,0 +1,46 @@
/**
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference path="../../adonis-typings/index.d.ts" />
import { SessionDriverContract, SessionConfig } from '@ioc:Adonis/Addons/Session';
import { RedisManagerContract } from '@ioc:Adonis/Addons/Redis';
/**
* File driver to read/write session to filesystem
*/
export declare class RedisDriver implements SessionDriverContract {
private config;
private redis;
/**
* Convert milliseconds to seconds
*/
private ttl;
constructor(config: SessionConfig, redis: RedisManagerContract);
/**
* Returns instance of the redis connection
*/
private getRedisConnection;
/**
* Returns file contents. A new file will be created if it's
* missing.
*/
read(sessionId: string): Promise<{
[key: string]: any;
} | null>;
/**
* Write session values to a file
*/
write(sessionId: string, values: Object): Promise<void>;
/**
* Cleanup session file by removing it
*/
destroy(sessionId: string): Promise<void>;
/**
* Updates the value expiry
*/
touch(sessionId: string): Promise<void>;
}
+73
View File
@@ -0,0 +1,73 @@
"use strict";
/**
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedisDriver = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
/**
* File driver to read/write session to filesystem
*/
class RedisDriver {
constructor(config, redis) {
this.config = config;
this.redis = redis;
/**
* Convert milliseconds to seconds
*/
this.ttl = Math.round((typeof this.config.age === 'string' ? helpers_1.string.toMs(this.config.age) : this.config.age) / 1000);
if (!this.config.redisConnection) {
throw new utils_1.Exception('Missing redisConnection for session redis driver inside "config/session" file', 500, 'E_INVALID_SESSION_DRIVER_CONFIG');
}
}
/**
* Returns instance of the redis connection
*/
getRedisConnection() {
return this.redis.connection(this.config.redisConnection);
}
/**
* Returns file contents. A new file will be created if it's
* missing.
*/
async read(sessionId) {
const contents = await this.getRedisConnection().get(sessionId);
if (!contents) {
return null;
}
const verifiedContents = new helpers_1.MessageBuilder().verify(contents, sessionId);
if (typeof verifiedContents !== 'object') {
return null;
}
return verifiedContents;
}
/**
* Write session values to a file
*/
async write(sessionId, values) {
if (typeof values !== 'object') {
throw new Error('Session file driver expects an object of values');
}
await this.getRedisConnection().setex(sessionId, this.ttl, new helpers_1.MessageBuilder().build(values, undefined, sessionId));
}
/**
* Cleanup session file by removing it
*/
async destroy(sessionId) {
await this.getRedisConnection().del(sessionId);
}
/**
* Updates the value expiry
*/
async touch(sessionId) {
await this.getRedisConnection().expire(sessionId, this.ttl);
}
}
exports.RedisDriver = RedisDriver;
+193
View File
@@ -0,0 +1,193 @@
/// <reference path="../../adonis-typings/session.d.ts" />
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { SessionConfig, SessionContract, AllowedSessionValues, SessionDriverContract } from '@ioc:Adonis/Addons/Session';
import { Store } from '../Store';
/**
* Session class exposes the API to read/write values to the session for
* a given request.
*/
export declare class Session implements SessionContract {
private ctx;
private config;
private driver;
/**
* Set to true inside the `initiate` method
*/
initiated: boolean;
/**
* A boolean to know if it's a fresh session or not. Fresh
* sessions are those, whose session id is not present
* in cookie
*/
fresh: boolean;
/**
* A boolean to know if store is initiated in readonly mode
* or not. This is done during Websocket requests
*/
readonly: boolean;
/**
* Session id for the given request. A new session id is only
* generated when the cookie for the session id is missing
*/
sessionId: string;
/**
* A copy of previously set flash messages
*/
flashMessages: Store;
/**
* Session id for the current request. It will be different
* from the "this.sessionId" when regenerate is called.
*/
private currentSessionId;
/**
* A instance of store with values read from the driver. The store
* in initiated inside the [[initiate]] method
*/
private store;
/**
* Whether or not to re-generate the session id before comitting
* session values.
*/
private regeneratedSessionId;
/**
* A copy of flash messages. The `input` messages
* are overwritten when any of the input related
* methods are used.
*
* The `others` object is expanded with each call.
*/
responseFlashMessages: Store;
/**
* Session key for setting flash messages
*/
private flashMessagesKey;
constructor(ctx: HttpContextContract, config: SessionConfig, driver: SessionDriverContract);
/**
* Returns a merged copy of flash messages or null
* when nothing is set
*/
private setFlashMessages;
/**
* Returns the existing session id or creates one.
*/
private getSessionId;
/**
* Ensures the session store is initialized
*/
private ensureIsReady;
/**
* Raises exception when session store is in readonly mode
*/
private ensureIsMutable;
/**
* Touches the session cookie
*/
private touchSessionCookie;
/**
* Commits the session value to the store
*/
private commitValuesToStore;
/**
* Touches the driver to make sure the session values doesn't expire
*/
private touchDriver;
/**
* Reading flash messages from the last HTTP request and
* updating the flash messages bag
*/
private readLastRequestFlashMessage;
/**
* Share flash messages & read only session's functions with views
* (only when view property exists)
*/
private shareLocalsWithView;
/**
* Initiating the session by reading it's value from the
* driver and feeding it to a store.
*
* Multiple calls to `initiate` results in a noop.
*/
initiate(readonly: boolean): Promise<void>;
/**
* Re-generates the session id. This can is used to avoid
* session fixation attacks.
*/
regenerate(): void;
/**
* Set/update session value
*/
put(key: string, value: AllowedSessionValues): void;
/**
* Find if the value exists in the session
*/
has(key: string): boolean;
/**
* Get value from the session. The default value is returned
* when actual value is `undefined`
*/
get(key: string, defaultValue?: any): any;
/**
* Returns everything from the session
*/
all(): any;
/**
* Remove value for a given key from the session
*/
forget(key: string): void;
/**
* The method is equivalent to calling `session.get` followed
* by `session.forget`
*/
pull(key: string, defaultValue?: any): any;
/**
* Increment value for a number inside the session store. The
* method raises an error when underlying value is not
* a number
*/
increment(key: string, steps?: number): void;
/**
* Decrement value for a number inside the session store. The
* method raises an error when underlying value is not
* a number
*/
decrement(key: string, steps?: number): void;
/**
* Remove everything from the session
*/
clear(): void;
/**
* Add a new flash message
*/
flash(key: string | {
[key: string]: AllowedSessionValues;
}, value?: AllowedSessionValues): void;
/**
* Flash all form values
*/
flashAll(): void;
/**
* Flash all form values except mentioned keys
*/
flashExcept(keys: string[]): void;
/**
* Flash only defined keys from the form values
*/
flashOnly(keys: string[]): void;
/**
* Reflash existing flash messages
*/
reflash(): void;
/**
* Reflash selected keys from the existing flash messages
*/
reflashOnly(keys: string[]): void;
/**
* Omit selected keys from the existing flash messages
* and flash the rest of values
*/
reflashExcept(keys: string[]): void;
/**
* Writes value to the underlying session driver.
*/
commit(): Promise<void>;
}
+352
View File
@@ -0,0 +1,352 @@
"use strict";
/*
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Session = void 0;
/// <reference path="../../adonis-typings/session.ts" />
const utils_1 = require("@poppinss/utils");
const helpers_1 = require("@poppinss/utils/build/helpers");
const Store_1 = require("../Store");
/**
* Session class exposes the API to read/write values to the session for
* a given request.
*/
class Session {
constructor(ctx, config, driver) {
this.ctx = ctx;
this.config = config;
this.driver = driver;
/**
* Set to true inside the `initiate` method
*/
this.initiated = false;
/**
* A boolean to know if it's a fresh session or not. Fresh
* sessions are those, whose session id is not present
* in cookie
*/
this.fresh = false;
/**
* A boolean to know if store is initiated in readonly mode
* or not. This is done during Websocket requests
*/
this.readonly = false;
/**
* Session id for the given request. A new session id is only
* generated when the cookie for the session id is missing
*/
this.sessionId = this.getSessionId();
/**
* A copy of previously set flash messages
*/
this.flashMessages = new Store_1.Store({});
/**
* Session id for the current request. It will be different
* from the "this.sessionId" when regenerate is called.
*/
this.currentSessionId = this.sessionId;
/**
* Whether or not to re-generate the session id before comitting
* session values.
*/
this.regeneratedSessionId = false;
/**
* A copy of flash messages. The `input` messages
* are overwritten when any of the input related
* methods are used.
*
* The `others` object is expanded with each call.
*/
this.responseFlashMessages = new Store_1.Store({});
/**
* Session key for setting flash messages
*/
this.flashMessagesKey = '__flash__';
}
/**
* Returns a merged copy of flash messages or null
* when nothing is set
*/
setFlashMessages() {
if (this.responseFlashMessages.isEmpty) {
return;
}
const { input, ...others } = this.responseFlashMessages.all();
this.put(this.flashMessagesKey, { ...input, ...others });
}
/**
* Returns the existing session id or creates one.
*/
getSessionId() {
const sessionId = this.ctx.request.cookie(this.config.cookieName);
if (sessionId) {
this.ctx.logger.trace('existing session found');
return sessionId;
}
this.fresh = true;
this.ctx.logger.trace('generating new session id');
return (0, helpers_1.cuid)();
}
/**
* Ensures the session store is initialized
*/
ensureIsReady() {
if (!this.initiated) {
throw new utils_1.Exception('Session store is not initiated yet. Make sure you are using the session hook', 500, 'E_RUNTIME_EXCEPTION');
}
}
/**
* Raises exception when session store is in readonly mode
*/
ensureIsMutable() {
if (this.readonly) {
throw new utils_1.Exception('Session store is in readonly mode and cannot be mutated', 500, 'E_RUNTIME_EXCEPTION');
}
}
/**
* Touches the session cookie
*/
touchSessionCookie() {
this.ctx.logger.trace('touching session cookie');
this.ctx.response.cookie(this.config.cookieName, this.sessionId, this.config.cookie);
}
/**
* Commits the session value to the store
*/
async commitValuesToStore() {
this.ctx.logger.trace('persist session store with driver');
await this.driver.write(this.sessionId, this.store.toJSON());
}
/**
* Touches the driver to make sure the session values doesn't expire
*/
async touchDriver() {
this.ctx.logger.trace('touch driver for liveliness');
await this.driver.touch(this.sessionId);
}
/**
* Reading flash messages from the last HTTP request and
* updating the flash messages bag
*/
readLastRequestFlashMessage() {
if (this.readonly) {
return;
}
this.flashMessages.update(this.pull(this.flashMessagesKey, null));
}
/**
* Share flash messages & read only session's functions with views
* (only when view property exists)
*/
shareLocalsWithView() {
if (!this.ctx['view'] || typeof this.ctx['view'].share !== 'function') {
return;
}
this.ctx['view'].share({
flashMessages: this.flashMessages,
session: {
get: this.get.bind(this),
has: this.has.bind(this),
all: this.all.bind(this),
},
});
}
/**
* Initiating the session by reading it's value from the
* driver and feeding it to a store.
*
* Multiple calls to `initiate` results in a noop.
*/
async initiate(readonly) {
if (this.initiated) {
return;
}
this.readonly = readonly;
/**
* Profiling the driver read method
*/
await this.ctx.profiler.profileAsync('session:initiate', { driver: this.config.driver }, async () => {
const contents = await this.driver.read(this.sessionId);
this.store = new Store_1.Store(contents);
});
this.initiated = true;
this.readLastRequestFlashMessage();
this.shareLocalsWithView();
}
/**
* Re-generates the session id. This can is used to avoid
* session fixation attacks.
*/
regenerate() {
this.ctx.logger.trace('explicitly re-generating session id');
this.sessionId = (0, helpers_1.cuid)();
this.regeneratedSessionId = true;
}
/**
* Set/update session value
*/
put(key, value) {
this.ensureIsReady();
this.ensureIsMutable();
this.store.set(key, value);
}
/**
* Find if the value exists in the session
*/
has(key) {
this.ensureIsReady();
return this.store.has(key);
}
/**
* Get value from the session. The default value is returned
* when actual value is `undefined`
*/
get(key, defaultValue) {
this.ensureIsReady();
return this.store.get(key, defaultValue);
}
/**
* Returns everything from the session
*/
all() {
this.ensureIsReady();
return this.store.all();
}
/**
* Remove value for a given key from the session
*/
forget(key) {
this.ensureIsReady();
this.ensureIsMutable();
this.store.unset(key);
}
/**
* The method is equivalent to calling `session.get` followed
* by `session.forget`
*/
pull(key, defaultValue) {
this.ensureIsReady();
this.ensureIsMutable();
return this.store.pull(key, defaultValue);
}
/**
* Increment value for a number inside the session store. The
* method raises an error when underlying value is not
* a number
*/
increment(key, steps = 1) {
this.ensureIsReady();
this.ensureIsMutable();
this.store.increment(key, steps);
}
/**
* Decrement value for a number inside the session store. The
* method raises an error when underlying value is not
* a number
*/
decrement(key, steps = 1) {
this.ensureIsReady();
this.ensureIsMutable();
this.store.decrement(key, steps);
}
/**
* Remove everything from the session
*/
clear() {
this.ensureIsReady();
this.ensureIsMutable();
this.store.clear();
}
/**
* Add a new flash message
*/
flash(key, value) {
this.ensureIsReady();
this.ensureIsMutable();
/**
* Update value
*/
if (typeof key === 'string') {
if (value) {
this.responseFlashMessages.set(key, value);
}
}
else {
this.responseFlashMessages.merge(key);
}
}
/**
* Flash all form values
*/
flashAll() {
this.ensureIsReady();
this.ensureIsMutable();
this.responseFlashMessages.set('input', this.ctx.request.original());
}
/**
* Flash all form values except mentioned keys
*/
flashExcept(keys) {
this.ensureIsReady();
this.ensureIsMutable();
this.responseFlashMessages.set('input', utils_1.lodash.omit(this.ctx.request.original(), keys));
}
/**
* Flash only defined keys from the form values
*/
flashOnly(keys) {
this.ensureIsReady();
this.ensureIsMutable();
this.responseFlashMessages.set('input', utils_1.lodash.pick(this.ctx.request.original(), keys));
}
/**
* Reflash existing flash messages
*/
reflash() {
this.flash(this.flashMessages.all());
}
/**
* Reflash selected keys from the existing flash messages
*/
reflashOnly(keys) {
this.flash(utils_1.lodash.pick(this.flashMessages.all(), keys));
}
/**
* Omit selected keys from the existing flash messages
* and flash the rest of values
*/
reflashExcept(keys) {
this.flash(utils_1.lodash.omit(this.flashMessages.all(), keys));
}
/**
* Writes value to the underlying session driver.
*/
async commit() {
await this.ctx.profiler.profileAsync('session:commit', { driver: this.config.driver }, async () => {
if (!this.initiated) {
this.touchSessionCookie();
await this.touchDriver();
return;
}
/**
* Cleanup old session and re-generate new session
*/
if (this.regeneratedSessionId) {
await this.driver.destroy(this.currentSessionId);
}
/**
* Touch the session cookie to keep it alive.
*/
this.touchSessionCookie();
this.setFlashMessages();
await this.commitValuesToStore();
});
}
}
exports.Session = Session;
+78
View File
@@ -0,0 +1,78 @@
/**
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/// <reference types="@adonisjs/application/build/adonis-typings" />
import { ApplicationContract } from '@ioc:Adonis/Core/Application';
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { SessionConfig, ExtendCallback, SessionManagerContract, SessionClientContract } from '@ioc:Adonis/Addons/Session';
import { Session } from '../Session';
/**
* Session manager exposes the API to create session instance for a given
* request and also add new drivers.
*/
export declare class SessionManager implements SessionManagerContract {
application: ApplicationContract;
/**
* A private map of drivers added from outside in.
*/
private extendedDrivers;
/**
* Reference to session config
*/
private config;
constructor(application: ApplicationContract, config: SessionConfig);
/**
* Validates the config
*/
private validateConfig;
/**
* Processes the config and decides the `expires` option for the cookie
*/
private processConfig;
/**
* Returns an instance of cookie driver
*/
private createCookieDriver;
/**
* Returns an instance of the memory driver
*/
private createMemoryDriver;
/**
* Returns an instance of file driver
*/
private createFileDriver;
/**
* Returns an instance of redis driver
*/
private createRedisDriver;
/**
* Creates an instance of extended driver
*/
private createExtendedDriver;
/**
* Creates an instance of driver by looking at the config value `driver`.
* An hard exception is raised in case of invalid driver name
*/
private createDriver;
/**
* Find if the sessions are enabled
*/
isEnabled(): boolean;
/**
* Creates an instance of the session client
*/
client(): SessionClientContract;
/**
* Creates a new session instance for a given HTTP request
*/
create(ctx: HttpContextContract): Session;
/**
* Extend the drivers list by adding a new one.
*/
extend(driver: string, callback: ExtendCallback): void;
}
+148
View File
@@ -0,0 +1,148 @@
"use strict";
/**
* @adonisjs/session
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionManager = void 0;
const helpers_1 = require("@poppinss/utils/build/helpers");
const utils_1 = require("@poppinss/utils");
const Session_1 = require("../Session");
/**
* Session manager exposes the API to create session instance for a given
* request and also add new drivers.
*/
class SessionManager {
constructor(application, config) {
this.application = application;
/**
* A private map of drivers added from outside in.
*/
this.extendedDrivers = new Map();
this.validateConfig(config);
this.processConfig(config);
}
/**
* Validates the config
*/
validateConfig(config) {
const validator = new utils_1.ManagerConfigValidator(config, 'session', 'config/session');
validator.validateDefault('driver');
}
/**
* Processes the config and decides the `expires` option for the cookie
*/
processConfig(config) {
/**
* Explicitly overwriting `cookie.expires` and `cookie.maxAge` from
* the user defined config
*/
const processedConfig = Object.assign({ enabled: true }, config, {
cookie: {
...config.cookie,
expires: undefined,
maxAge: undefined,
},
});
/**
* Set the max age when `clearWithBrowser = false`. Otherwise cookie
* is a session cookie
*/
if (!processedConfig.clearWithBrowser) {
const age = typeof processedConfig.age === 'string'
? Math.round(helpers_1.string.toMs(processedConfig.age) / 1000)
: processedConfig.age;
processedConfig.cookie.maxAge = age;
}
this.config = processedConfig;
}
/**
* Returns an instance of cookie driver
*/
createCookieDriver(ctx) {
const { CookieDriver } = require('../Drivers/Cookie');
return new CookieDriver(this.config, ctx);
}
/**
* Returns an instance of the memory driver
*/
createMemoryDriver() {
const { MemoryDriver } = require('../Drivers/Memory');
return new MemoryDriver();
}
/**
* Returns an instance of file driver
*/
createFileDriver() {
const { FileDriver } = require('../Drivers/File');
return new FileDriver(this.config);
}
/**
* Returns an instance of redis driver
*/
createRedisDriver() {
const { RedisDriver } = require('../Drivers/Redis');
if (!this.application.container.hasBinding('Adonis/Addons/Redis')) {
throw new Error('Install "@adonisjs/redis" in order to use the redis driver for storing sessions');
}
return new RedisDriver(this.config, this.application.container.use('Adonis/Addons/Redis'));
}
/**
* Creates an instance of extended driver
*/
createExtendedDriver(ctx) {
if (!this.extendedDrivers.has(this.config.driver)) {
throw new utils_1.Exception(`"${this.config.driver}" is not a valid session driver`, 500, 'E_INVALID_SESSION_DRIVER');
}
return this.extendedDrivers.get(this.config.driver)(this, this.config, ctx);
}
/**
* Creates an instance of driver by looking at the config value `driver`.
* An hard exception is raised in case of invalid driver name
*/
createDriver(ctx) {
switch (this.config.driver) {
case 'cookie':
return this.createCookieDriver(ctx);
case 'file':
return this.createFileDriver();
case 'redis':
return this.createRedisDriver();
case 'memory':
return this.createMemoryDriver();
default:
return this.createExtendedDriver(ctx);
}
}
/**
* Find if the sessions are enabled
*/
isEnabled() {
return this.config.enabled;
}
/**
* Creates an instance of the session client
*/
client() {
const { SessionClient } = require('../Client');
const CookieClient = this.application.container.resolveBinding('Adonis/Core/CookieClient');
return new SessionClient(this.config, this.createMemoryDriver(), CookieClient, {});
}
/**
* Creates a new session instance for a given HTTP request
*/
create(ctx) {
return new Session_1.Session(ctx, this.config, this.createDriver(ctx));
}
/**
* Extend the drivers list by adding a new one.
*/
extend(driver, callback) {
this.extendedDrivers.set(driver, callback);
}
}
exports.SessionManager = SessionManager;
+82
View File
@@ -0,0 +1,82 @@
/// <reference path="../../adonis-typings/index.d.ts" />
import { AllowedSessionValues, StoreContract } from '@ioc:Adonis/Addons/Session';
/**
* Session store to mutate and access values from the session object
*/
export declare class Store implements StoreContract {
/**
* Underlying store values
*/
private values;
constructor(values: {
[key: string]: any;
} | null);
/**
* Find if store is empty or not
*/
get isEmpty(): boolean;
/**
* Set key/value pair
*/
set(key: string, value: AllowedSessionValues): void;
/**
* Get value for a given key
*/
get(key: string, defaultValue?: any): any;
/**
* Remove key
*/
unset(key: string): void;
/**
* Reset store by clearing it's values.
*/
clear(): void;
/**
* Pull value from the store. It is same as calling
* store.get and then store.unset
*/
pull(key: string, defaultValue?: any): any;
/**
* Increment number. The method raises an error when
* nderlying value is not a number
*/
increment(key: string, steps?: number): void;
/**
* Increment number. The method raises an error when
* nderlying value is not a number
*/
decrement(key: string, steps?: number): void;
/**
* Overwrite the underlying values object
*/
update(values: {
[key: string]: any;
}): void;
/**
* Update to merge values
*/
merge(values: {
[key: string]: any;
}): any;
/**
* A boolean to know if value exists. Extra guards to check
* arrays for it's length as well.
*/
has(key: string, checkForArraysLength?: boolean): boolean;
/**
* Get all values
*/
all(): any;
/**
* Returns object representation of values
*/
toObject(): any;
/**
* Returns the store values
*/
toJSON(): any;
/**
* Returns string representation of the store
*/
toString(): string;
}
+131
View File
@@ -0,0 +1,131 @@
"use strict";
/*
* @adonisjs/redis
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Store = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const utils_1 = require("@poppinss/utils");
/**
* Session store to mutate and access values from the session object
*/
class Store {
constructor(values) {
this.values = values || {};
}
/**
* Find if store is empty or not
*/
get isEmpty() {
return !this.values || Object.keys(this.values).length === 0;
}
/**
* Set key/value pair
*/
set(key, value) {
utils_1.lodash.set(this.values, key, value);
}
/**
* Get value for a given key
*/
get(key, defaultValue) {
return utils_1.lodash.get(this.values, key, defaultValue);
}
/**
* Remove key
*/
unset(key) {
utils_1.lodash.unset(this.values, key);
}
/**
* Reset store by clearing it's values.
*/
clear() {
this.update({});
}
/**
* Pull value from the store. It is same as calling
* store.get and then store.unset
*/
pull(key, defaultValue) {
return ((value) => {
this.unset(key);
return value;
})(this.get(key, defaultValue));
}
/**
* Increment number. The method raises an error when
* nderlying value is not a number
*/
increment(key, steps = 1) {
const value = this.get(key, 0);
if (typeof value !== 'number') {
throw new utils_1.Exception(`Cannot increment "${key}", since original value is not a number`);
}
this.set(key, value + steps);
}
/**
* Increment number. The method raises an error when
* nderlying value is not a number
*/
decrement(key, steps = 1) {
const value = this.get(key, 0);
if (typeof value !== 'number') {
throw new utils_1.Exception(`Cannot increment "${key}", since original value is not a number`);
}
this.set(key, value - steps);
}
/**
* Overwrite the underlying values object
*/
update(values) {
this.values = values;
}
/**
* Update to merge values
*/
merge(values) {
utils_1.lodash.merge(this.values, values);
}
/**
* A boolean to know if value exists. Extra guards to check
* arrays for it's length as well.
*/
has(key, checkForArraysLength = true) {
const value = this.get(key);
if (!Array.isArray(value)) {
return !!value;
}
return checkForArraysLength ? value.length > 0 : !!value;
}
/**
* Get all values
*/
all() {
return this.values;
}
/**
* Returns object representation of values
*/
toObject() {
return this.all();
}
/**
* Returns the store values
*/
toJSON() {
return this.all();
}
/**
* Returns string representation of the store
*/
toString() {
return JSON.stringify(this.all());
}
}
exports.Store = Store;