Skip to content

Commit

Permalink
add magicAuthLink example
Browse files Browse the repository at this point in the history
  • Loading branch information
borisno2 authored and dcousens committed Feb 4, 2025
1 parent a921f3e commit 0789563
Show file tree
Hide file tree
Showing 13 changed files with 604 additions and 38 deletions.
66 changes: 66 additions & 0 deletions examples/auth-magic-link/keystone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { config } from '@keystone-6/core'
import { statelessSessions } from '@keystone-6/core/session'
import { createAuth } from '@keystone-6/auth'
import {
type Session,
lists,
extendGraphqlSchema,
} from './schema'
import type { TypeInfo } from '.keystone/types'

// WARNING: this example is for demonstration purposes only
// as with each of our examples, it has not been vetted
// or tested for any particular usage

// WARNING: you need to change this
const sessionSecret = '-- DEV COOKIE SECRET; CHANGE ME --'

// statelessSessions uses cookies for session tracking
// these cookies have an expiry, in seconds
// we use an expiry of one hour for this example
const sessionMaxAge = 60 * 60

// withAuth is a function we can use to wrap our base configuration
const { withAuth } = createAuth({
// this is the list that contains our users
listKey: 'User',

// an identity field, typically a username or an email address
identityField: 'name',

// a secret field must be a password field type
secretField: 'password',

// initFirstItem enables the "First User" experience, this will add an interface form
// adding a new User item if the database is empty
//
// WARNING: do not use initFirstItem in production
// see https://keystonejs.com/docs/config/auth#init-first-item for more
initFirstItem: {
// the following fields are used by the "Create First User" form
fields: ['name', 'password'],
},
})

export default withAuth<TypeInfo<Session>>(
config<TypeInfo>({
db: {
provider: 'sqlite',
url: process.env.DATABASE_URL ?? 'file:./keystone-example.db',

// WARNING: this is only needed for our monorepo examples, dont do this
prismaClientPath: 'node_modules/myprisma',
},
lists,
graphql: {
extendGraphqlSchema,
},
// you can find out more at https://keystonejs.com/docs/apis/session#session-api
session: 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
secret: sessionSecret,
}),
})
)
21 changes: 21 additions & 0 deletions examples/auth-magic-link/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@keystone-6/example-auth-magic-link",
"version": null,
"private": true,
"license": "MIT",
"scripts": {
"dev": "keystone dev",
"start": "keystone start",
"build": "keystone build",
"postinstall": "keystone postinstall"
},
"dependencies": {
"@keystone-6/auth": "^8.1.0",
"@keystone-6/core": "^6.3.1",
"@prisma/client": "5.19.0"
},
"devDependencies": {
"prisma": "5.19.0",
"typescript": "^5.5.0"
}
}
7 changes: 7 additions & 0 deletions examples/auth-magic-link/sandbox.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"template": "node",
"container": {
"startScript": "keystone dev",
"node": "16"
}
}
266 changes: 266 additions & 0 deletions examples/auth-magic-link/schema.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
# This file is automatically generated by Keystone, do not modify it manually.
# Modify your Keystone config when you want to change this.

type User {
id: ID!
name: String
password: PasswordState
}

type PasswordState {
isSet: Boolean!
}

input UserWhereUniqueInput {
id: ID
name: String
}

input UserWhereInput {
AND: [UserWhereInput!]
OR: [UserWhereInput!]
NOT: [UserWhereInput!]
id: IDFilter
name: StringFilter
}

input IDFilter {
equals: ID
in: [ID!]
notIn: [ID!]
lt: ID
lte: ID
gt: ID
gte: ID
not: IDFilter
}

input StringFilter {
equals: String
in: [String!]
notIn: [String!]
lt: String
lte: String
gt: String
gte: String
contains: String
startsWith: String
endsWith: String
not: NestedStringFilter
}

input NestedStringFilter {
equals: String
in: [String!]
notIn: [String!]
lt: String
lte: String
gt: String
gte: String
contains: String
startsWith: String
endsWith: String
not: NestedStringFilter
}

input UserOrderByInput {
id: OrderDirection
name: OrderDirection
}

enum OrderDirection {
asc
desc
}

input UserUpdateInput {
name: String
password: String
}

input UserUpdateArgs {
where: UserWhereUniqueInput!
data: UserUpdateInput!
}

input UserCreateInput {
name: String
password: String
}

"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")

type Mutation {
createUser(data: UserCreateInput!): User
createUsers(data: [UserCreateInput!]!): [User]
updateUser(where: UserWhereUniqueInput!, data: UserUpdateInput!): User
updateUsers(data: [UserUpdateArgs!]!): [User]
deleteUser(where: UserWhereUniqueInput!): User
deleteUsers(where: [UserWhereUniqueInput!]!): [User]
endSession: Boolean!
authenticateUserWithPassword(name: String!, password: String!): UserAuthenticationWithPasswordResult
createInitialUser(data: CreateInitialUserInput!): UserAuthenticationWithPasswordSuccess!
requestAuthToken(userId: String!): Boolean!
redeemAuthToken(userId: String!, token: String!): Boolean!
}

union UserAuthenticationWithPasswordResult = UserAuthenticationWithPasswordSuccess | UserAuthenticationWithPasswordFailure

type UserAuthenticationWithPasswordSuccess {
sessionToken: String!
item: User!
}

type UserAuthenticationWithPasswordFailure {
message: String!
}

input CreateInitialUserInput {
name: String
password: String
}

type Query {
user(where: UserWhereUniqueInput!): User
users(where: UserWhereInput! = {}, orderBy: [UserOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: UserWhereUniqueInput): [User!]
usersCount(where: UserWhereInput! = {}): Int
keystone: KeystoneMeta!
authenticatedItem: User
}

type KeystoneMeta {
adminMeta: KeystoneAdminMeta!
}

type KeystoneAdminMeta {
lists: [KeystoneAdminUIListMeta!]!
list(key: String!): KeystoneAdminUIListMeta
}

type KeystoneAdminUIListMeta {
key: String!
path: String!
description: String
label: String!
labelField: String!
singular: String!
plural: String!
fields: [KeystoneAdminUIFieldMeta!]!
groups: [KeystoneAdminUIFieldGroupMeta!]!
graphql: KeystoneAdminUIGraphQL!
pageSize: Int!
initialColumns: [String!]!
initialSearchFields: [String!]!
initialSort: KeystoneAdminUISort
isSingleton: Boolean!
hideNavigation: Boolean!
hideCreate: Boolean!
hideDelete: Boolean!
}

type KeystoneAdminUIFieldMeta {
path: String!
label: String!
description: String
isOrderable: Boolean!
isFilterable: Boolean!
isNonNull: [KeystoneAdminUIFieldMetaIsNonNull!]
fieldMeta: JSON
viewsIndex: Int!
customViewsIndex: Int
createView: KeystoneAdminUIFieldMetaCreateView!
listView: KeystoneAdminUIFieldMetaListView!
itemView(id: ID): KeystoneAdminUIFieldMetaItemView
search: QueryMode
}

enum KeystoneAdminUIFieldMetaIsNonNull {
read
create
update
}

type KeystoneAdminUIFieldMetaCreateView {
fieldMode: KeystoneAdminUIFieldMetaCreateViewFieldMode!
}

enum KeystoneAdminUIFieldMetaCreateViewFieldMode {
edit
hidden
}

type KeystoneAdminUIFieldMetaListView {
fieldMode: KeystoneAdminUIFieldMetaListViewFieldMode!
}

enum KeystoneAdminUIFieldMetaListViewFieldMode {
read
hidden
}

type KeystoneAdminUIFieldMetaItemView {
fieldMode: KeystoneAdminUIFieldMetaItemViewFieldMode
fieldPosition: KeystoneAdminUIFieldMetaItemViewFieldPosition
}

enum KeystoneAdminUIFieldMetaItemViewFieldMode {
edit
read
hidden
}

enum KeystoneAdminUIFieldMetaItemViewFieldPosition {
form
sidebar
}

enum QueryMode {
default
insensitive
}

type KeystoneAdminUIFieldGroupMeta {
label: String!
description: String
fields: [KeystoneAdminUIFieldMeta!]!
}

type KeystoneAdminUIGraphQL {
names: KeystoneAdminUIGraphQLNames!
}

type KeystoneAdminUIGraphQLNames {
outputTypeName: String!
whereInputName: String!
whereUniqueInputName: String!
createInputName: String!
createMutationName: String!
createManyMutationName: String!
relateToOneForCreateInputName: String!
relateToManyForCreateInputName: String!
itemQueryName: String!
listOrderName: String!
listQueryCountName: String!
listQueryName: String!
updateInputName: String!
updateMutationName: String!
updateManyInputName: String!
updateManyMutationName: String!
relateToOneForUpdateInputName: String!
relateToManyForUpdateInputName: String!
deleteMutationName: String!
deleteManyMutationName: String!
}

type KeystoneAdminUISort {
field: String!
direction: KeystoneAdminUISortDirection!
}

enum KeystoneAdminUISortDirection {
ASC
DESC
}
21 changes: 21 additions & 0 deletions examples/auth-magic-link/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// This file is automatically generated by Keystone, do not modify it manually.
// Modify your Keystone config when you want to change this.

datasource sqlite {
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
provider = "sqlite"
}

generator client {
provider = "prisma-client-js"
output = "node_modules/myprisma"
}

model User {
id String @id @default(cuid())
name String @unique @default("")
password String
oneTimeToken String?
oneTimeTokenCreatedAt DateTime?
}
Loading

0 comments on commit 0789563

Please sign in to comment.