Skip to content

Commit

Permalink
change core from sessionStrategy to getSession
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Calder committed Nov 17, 2022
1 parent 51ae55c commit e3a26d6
Show file tree
Hide file tree
Showing 32 changed files with 275 additions and 253 deletions.
18 changes: 9 additions & 9 deletions examples/auth/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ const { withAuth } = createAuth({
// isEnabled: true,
},
},
sessionStrategy:
// Stateless sessions will store the listKey and itemId of the signed-in user in a cookie
statelessSessions({
data: 'name isAdmin',
// The maxAge option controls how long session cookies are valid for before they expire
maxAge: sessionMaxAge,
// The session secret is used to encrypt cookie data (should be an environment variable)
secret: sessionSecret,
}),
// Populate session.data based on the authed user
sessionData: 'name isAdmin',
/* TODO -- complete the UI for these features and enable them
passwordResetLink: {
sendToken(args) {
Expand All @@ -65,13 +73,5 @@ export default withAuth(
},
lists,
ui: {},
session:
// Stateless sessions will store the listKey and itemId of the signed-in user in a cookie
statelessSessions({
// The maxAge option controls how long session cookies are valid for before they expire
maxAge: sessionMaxAge,
// The session secret is used to encrypt cookie data (should be an environment variable)
secret: sessionSecret,
}),
})
);
14 changes: 6 additions & 8 deletions examples/basic/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ const auth = createAuth({
isAdmin: true,
},
},
sessionData: 'name isAdmin',
sessionStrategy: statelessSessions({
data: 'name isAdmin',
// The session secret is used to encrypt cookie data (should be an environment variable)
maxAge: sessionMaxAge,
secret: sessionSecret,
}),
});

// TODO -- Create a separate example for access control in the Admin UI
Expand Down Expand Up @@ -56,12 +61,5 @@ export default auth.withAuth(
},
lists,
extendGraphqlSchema,
session: statelessSessions({ maxAge: sessionMaxAge, secret: sessionSecret }),
// TODO -- Create a separate example for stored/redis sessions
// session: storedSessions({
// store: new Map(),
// // store: redisSessionStore({ client: redis.createClient() }),
// secret: sessionSecret,
// }),
})
);
131 changes: 61 additions & 70 deletions examples/custom-session-validation/keystone.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
import { KeystoneConfig, SessionStrategy } from '@keystone-6/core/types';
import { config } from '@keystone-6/core';
import { statelessSessions } from '@keystone-6/auth/session';
import { SessionStrategy } from '@keystone-6/auth/types';
import { createAuth } from '@keystone-6/auth';
import { lists } from './schema';
import { Context, TypeInfo } from '.keystone/types';

const withTimeData = (
_sessionStrategy: SessionStrategy<Record<string, any>>
): SessionStrategy<Record<string, any>> => {
const { start, ...sessionStrategy } = _sessionStrategy;
return {
...sessionStrategy,
start: async ({ data, context }) => {
// Add the current time to the session data
const withTimeData = {
...data,
startTime: new Date(),
};
// Start the keystone session and include the startTime
return await start({ data: withTimeData, context });
},
};
};

const maxSessionAge = 60 * 60 * 8; // 8 hours, in seconds
// Stateless sessions will store the listKey and itemId of the signed-in user in a cookie.
// This session object will be made available on the context object used in hooks, access-control,
// resolvers, etc.

// createAuth configures signin functionality based on the config below. Note this only implements
// authentication, i.e signing in as an item using identity and secret fields in a list. Session
Expand All @@ -22,79 +46,46 @@ const { withAuth } = createAuth({
fields: ['name', 'email', 'password'],
},
// Make passwordChangedAt available on the sesssion data
sessionData: 'id passwordChangedAt',
sessionStrategy: withTimeData(
statelessSessions({
data: 'id passwordChangedAt',
// The session secret is used to encrypt cookie data (should be an environment variable)
maxAge: maxSessionAge,
secret: '-- EXAMPLE COOKIE SECRET; CHANGE ME --',
})
),
});

const maxSessionAge = 60 * 60 * 8; // 8 hours, in seconds
// Stateless sessions will store the listKey and itemId of the signed-in user in a cookie.
// This session object will be made available on the context object used in hooks, access-control,
// resolvers, etc.
async function getSession({ context }: { context: Context }): Promise<unknown> {
// Get the session from the cookie stored by keystone
const { session } = context;
// If there is no session returned from keystone or there is no startTime on the session return an invalid session
// If session.startTime is null session.data.passwordChangedAt > session.startTime will always be true and therefore
// the session will never be invalid until the maxSessionAge is reached.
if (!session || !session.startTime) return;
//if the password hasn't changed (and isn't missing), then the session is OK
if (session.data.passwordChangedAt === null) return session;
// If passwordChangeAt is undefined, then sessionData is missing the passwordChangedAt field
// Or something is wrong with the session configuration so throw and error
if (session.data.passwordChangedAt === undefined) {
throw new TypeError('passwordChangedAt is not listed in sessionData');
}
if (session.data.passwordChangedAt > session.startTime) {
return;
}

const withTimeData = (
_sessionStrategy: SessionStrategy<Record<string, any>>
): SessionStrategy<Record<string, any>> => {
const { get, start, ...sessionStrategy } = _sessionStrategy;
return {
...sessionStrategy,
get: async ({ context }) => {
// Get the session from the cookie stored by keystone
const session = await get({ context });
// If there is no session returned from keystone or there is no startTime on the session return an invalid session
// If session.startTime is null session.data.passwordChangedAt > session.startTime will always be true and therefore
// the session will never be invalid until the maxSessionAge is reached.
if (!session || !session.startTime) return;
//if the password hasn't changed (and isn't missing), then the session is OK
if (session.data.passwordChangedAt === null) return session;
// If passwordChangeAt is undefined, then sessionData is missing the passwordChangedAt field
// Or something is wrong with the session configuration so throw and error
if (session.data.passwordChangedAt === undefined) {
throw new TypeError('passwordChangedAt is not listed in sessionData');
}
if (session.data.passwordChangedAt > session.startTime) {
return;
}

return session;
},
start: async ({ data, context }) => {
// Add the current time to the session data
const withTimeData = {
...data,
startTime: new Date(),
};
// Start the keystone session and include the startTime
return await start({ data: withTimeData, context });
},
};
};

const myAuth = (keystoneConfig: KeystoneConfig): KeystoneConfig => {
// Add the session strategy to the config
if (!keystoneConfig.session) throw new TypeError('Missing .session configuration');
return {
...keystoneConfig,
session: withTimeData(keystoneConfig.session),
};
};

const session = statelessSessions({
// The session secret is used to encrypt cookie data (should be an environment variable)
maxAge: maxSessionAge,
secret: '-- EXAMPLE COOKIE SECRET; CHANGE ME --',
});
return session;
}

// We wrap our config using the withAuth function. This will inject all
// the extra config required to add support for authentication in our system.
export default myAuth(
withAuth(
config({
db: {
provider: 'sqlite',
url: process.env.DATABASE_URL || 'file:./keystone-example.db',
},
lists,
// We add our session configuration to the system here.
session,
})
)
export default withAuth<TypeInfo>(
config({
db: {
provider: 'sqlite',
url: process.env.DATABASE_URL || 'file:./keystone-example.db',
},
lists,
getSession,
})
);
4 changes: 2 additions & 2 deletions examples/ecommerce/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const databaseURL = process.env.DATABASE_URL || 'file:./keystone.db';
const sessionConfig = {
maxAge: 60 * 60 * 24 * 360, // How long they stay signed in?
secret: process.env.COOKIE_SECRET || 'this secret should only be used in testing',
data: `id name email role { ${permissionsList.join(' ')} }`,
};

const { withAuth } = createAuth({
Expand All @@ -36,7 +37,7 @@ const { withAuth } = createAuth({
await sendPasswordResetEmail(args.token, args.identity);
},
},
sessionData: `id name email role { ${permissionsList.join(' ')} }`,
sessionStrategy: statelessSessions(sessionConfig),
});

export default withAuth(
Expand Down Expand Up @@ -71,6 +72,5 @@ export default withAuth(
// Show the UI only for poeple who pass this test
isAccessAllowed: ({ session }) => !!session,
},
session: statelessSessions(sessionConfig),
})
);
44 changes: 21 additions & 23 deletions examples/redis-session-store/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,8 @@ import { createAuth } from '@keystone-6/auth';
import { createClient } from '@redis/client';
import { lists } from './schema';

// createAuth configures signin functionality based on the config below. Note this only implements
// authentication, i.e signing in as an item using identity and secret fields in a list. Session
// management and access control are controlled independently in the main keystone config.
const { withAuth } = createAuth({
// This is the list that contains items people can sign in as
listKey: 'Person',
// The identity field is typically a username or email address
identityField: 'email',
// The secret field must be a password type field
secretField: 'password',

// initFirstItem turns on the "First User" experience, which prompts you to create a new user
// when there are no items in the list yet
initFirstItem: {
// These fields are collected in the "Create First User" form
fields: ['name', 'email', 'password'],
},
});

const redis = createClient();

const session = storedSessions({
const sessionStrategy = storedSessions({
store: ({ maxAge }) => ({
async get(key) {
let result = await redis.get(key);
Expand All @@ -44,6 +24,26 @@ const session = storedSessions({
secret: '-- EXAMPLE COOKIE SECRET; CHANGE ME --',
});

// createAuth configures signin functionality based on the config below. Note this only implements
// authentication, i.e signing in as an item using identity and secret fields in a list. Session
// management and access control are controlled independently in the main keystone config.
const { withAuth } = createAuth({
// This is the list that contains items people can sign in as
listKey: 'Person',
// The identity field is typically a username or email address
identityField: 'email',
// The secret field must be a password type field
secretField: 'password',

// initFirstItem turns on the "First User" experience, which prompts you to create a new user
// when there are no items in the list yet
initFirstItem: {
// These fields are collected in the "Create First User" form
fields: ['name', 'email', 'password'],
},
sessionStrategy,
});

// We wrap our config using the withAuth function. This will inject all
// the extra config required to add support for authentication in our system.
export default withAuth(
Expand All @@ -56,7 +56,5 @@ export default withAuth(
},
},
lists,
// We add our session configuration to the system here.
session,
})
);
26 changes: 13 additions & 13 deletions examples/roles/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ const sessionMaxAge = 60 * 60 * 24 * 30; // 30 days
const sessionConfig = {
maxAge: sessionMaxAge,
secret: sessionSecret,
/* This loads the related role for the current user, including all permissions */
data: `
name role {
id
name
canCreateTodos
canManageAllTodos
canSeeOtherPeople
canEditOtherPeople
canManagePeople
canManageRoles
}`,
};

const { withAuth } = createAuth({
Expand All @@ -36,18 +48,7 @@ const { withAuth } = createAuth({
},
},
},
/* This loads the related role for the current user, including all permissions */
sessionData: `
name role {
id
name
canCreateTodos
canManageAllTodos
canSeeOtherPeople
canEditOtherPeople
canManagePeople
canManageRoles
}`,
sessionStrategy: statelessSessions(sessionConfig),
});

export default withAuth(
Expand All @@ -61,6 +62,5 @@ export default withAuth(
/* Everyone who is signed in can access the Admin UI */
isAccessAllowed: isSignedIn,
},
session: statelessSessions(sessionConfig),
})
);
20 changes: 10 additions & 10 deletions examples/testing/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { createAuth } from '@keystone-6/auth';
import { lists } from './schema';
import { TypeInfo } from '.keystone/types';

// Stateless sessions will store the listKey and itemId of the signed-in user in a cookie.
// This session object will be made available on the context object used in hooks, access-control,
// resolvers, etc.
const sessionStrategy = statelessSessions({
// The session secret is used to encrypt cookie data (should be an environment variable)
secret: '-- EXAMPLE COOKIE SECRET; CHANGE ME --',
});

// createAuth configures signin functionality based on the config below. Note this only implements
// authentication, i.e signing in as an item using identity and secret fields in a list. Session
// management and access control are controlled independently in the main keystone config.
Expand All @@ -21,14 +29,8 @@ const { withAuth } = createAuth({
// These fields are collected in the "Create First User" form
fields: ['name', 'email', 'password'],
},
});

// Stateless sessions will store the listKey and itemId of the signed-in user in a cookie.
// This session object will be made available on the context object used in hooks, access-control,
// resolvers, etc.
const session = statelessSessions({
// The session secret is used to encrypt cookie data (should be an environment variable)
secret: '-- EXAMPLE COOKIE SECRET; CHANGE ME --',
// We add our session configuration to the system here.
sessionStrategy,
});

// We wrap our config using the withAuth function. This will inject all
Expand All @@ -40,7 +42,5 @@ export default withAuth(
url: process.env.DATABASE_URL || 'file:./keystone-example.db',
},
lists,
// We add our session configuration to the system here.
session,
})
);
Loading

0 comments on commit e3a26d6

Please sign in to comment.