mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-26 10:35:04 +03:00
Serialize AuthUser properly (#1995)
This commit is contained in:
parent
76580648bc
commit
d6796bf7f1
@ -6,6 +6,4 @@ export {
|
||||
} from './user.js'
|
||||
|
||||
// PUBLIC API
|
||||
export {
|
||||
type AuthUser,
|
||||
} from '../server/auth/user.js';
|
||||
export { type AuthUser } from '../server/auth/user.js'
|
||||
|
@ -1,15 +1,15 @@
|
||||
{{={= =}=}}
|
||||
import { Request as ExpressRequest } from "express";
|
||||
|
||||
import { type {= userEntityUpper =} } from "wasp/entities"
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import { type {= userEntityUpper =} } from '../entities/index.js';
|
||||
import { type AuthUserData } from '../server/auth/user.js';
|
||||
|
||||
import { auth } from "./lucia.js";
|
||||
import type { Session } from "lucia";
|
||||
import { throwInvalidCredentialsError } from "./utils.js";
|
||||
|
||||
import { prisma } from 'wasp/server';
|
||||
import { createAuthUser } from "../server/auth/user.js";
|
||||
import { createAuthUserData } from "../server/auth/user.js";
|
||||
|
||||
// PRIVATE API
|
||||
// Creates a new session for the `authId` in the database
|
||||
@ -17,52 +17,42 @@ export async function createSession(authId: string): Promise<Session> {
|
||||
return auth.createSession(authId, {});
|
||||
}
|
||||
|
||||
type SessionAndUser = {
|
||||
session: Session;
|
||||
user: AuthUserData;
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<{
|
||||
user: AuthUser | null,
|
||||
session: Session | null,
|
||||
}> {
|
||||
export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<SessionAndUser | null> {
|
||||
const authorizationHeader = req.headers["authorization"];
|
||||
|
||||
if (typeof authorizationHeader !== "string") {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessionId = auth.readBearerToken(authorizationHeader);
|
||||
if (!sessionId) {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
return getSessionAndUserFromSessionId(sessionId);
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export async function getSessionAndUserFromSessionId(sessionId: string): Promise<{
|
||||
user: AuthUser | null,
|
||||
session: Session | null,
|
||||
}> {
|
||||
export async function getSessionAndUserFromSessionId(sessionId: string): Promise<SessionAndUser | null> {
|
||||
const { session, user: authEntity } = await auth.validateSession(sessionId);
|
||||
|
||||
if (!session || !authEntity) {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
session,
|
||||
user: await getUser(authEntity.userId)
|
||||
user: await getAuthUserData(authEntity.userId)
|
||||
}
|
||||
}
|
||||
|
||||
async function getUser(userId: {= userEntityUpper =}['id']): Promise<AuthUser> {
|
||||
async function getAuthUserData(userId: {= userEntityUpper =}['id']): Promise<AuthUserData> {
|
||||
const user = await prisma.{= userEntityLower =}
|
||||
.findUnique({
|
||||
where: { id: userId },
|
||||
@ -79,7 +69,7 @@ async function getUser(userId: {= userEntityUpper =}['id']): Promise<AuthUser> {
|
||||
throwInvalidCredentialsError()
|
||||
}
|
||||
|
||||
return createAuthUser(user);
|
||||
return createAuthUserData(user);
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
|
@ -4,7 +4,8 @@ import { useQuery, buildAndRegisterQuery } from 'wasp/client/operations'
|
||||
import type { QueryFunction, Query } from 'wasp/client/operations/rpc'
|
||||
import { api, handleApiError } from 'wasp/client/api'
|
||||
import { HttpMethod } from 'wasp/client'
|
||||
import type { AuthUser } from '../server/auth/user.js'
|
||||
import type { AuthUser, AuthUserData } from '../server/auth/user.js'
|
||||
import { makeAuthUserIfPossible } from '../auth/user.js'
|
||||
import { UseQueryResult } from '@tanstack/react-query'
|
||||
|
||||
// PUBLIC API
|
||||
@ -20,8 +21,9 @@ function createUserGetter(): Query<void, AuthUser | null> {
|
||||
const getMeRoute = { method: HttpMethod.Get, path: `/${getMeRelativePath}` }
|
||||
const getMe: QueryFunction<void, AuthUser | null> = async () => {
|
||||
try {
|
||||
const response = await api.get(getMeRoute.path)
|
||||
return superjsonDeserialize(response.data)
|
||||
const response = await api.get(getMeRoute.path)
|
||||
const userData = superjsonDeserialize<AuthUserData | null>(response.data)
|
||||
return makeAuthUserIfPossible(userData)
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
return null
|
||||
|
@ -1,6 +1,7 @@
|
||||
{{={= =}=}}
|
||||
import { type {= authIdentityEntityName =} } from '../entities/index.js'
|
||||
import { type ProviderName } from '../server/_types/index.js'
|
||||
import type { AuthUserData, AuthUser } from '../server/auth/user.js'
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
@ -26,6 +27,28 @@ export function getFirstProviderUserId(user?: UserEntityWithAuth): string | null
|
||||
return user.auth.identities[0].providerUserId ?? null;
|
||||
}
|
||||
|
||||
// PRIVATE API (used in SDK and server)
|
||||
export type { AuthUserData, AuthUser } from '../server/auth/user.js'
|
||||
|
||||
// PRIVATE API (used in SDK and server)
|
||||
export function makeAuthUserIfPossible(user: null): null
|
||||
export function makeAuthUserIfPossible(user: AuthUserData): AuthUser
|
||||
export function makeAuthUserIfPossible(
|
||||
user: AuthUserData | null,
|
||||
): AuthUser | null {
|
||||
return user ? makeAuthUser(user) : null
|
||||
}
|
||||
|
||||
function makeAuthUser(data: AuthUserData): AuthUser {
|
||||
return {
|
||||
...data,
|
||||
getFirstProviderUserId: () => {
|
||||
const identities = Object.values(data.identities).filter(Boolean);
|
||||
return identities.length > 0 ? identities[0].id : null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): {= authIdentityEntityName =} | null {
|
||||
if (!user.auth) {
|
||||
return null;
|
||||
|
@ -4,10 +4,10 @@ import { throwInvalidCredentialsError } from 'wasp/auth/utils'
|
||||
|
||||
/**
|
||||
* Auth middleware
|
||||
*
|
||||
*
|
||||
* If the request includes an `Authorization` header it will try to authenticate the request,
|
||||
* otherwise it will let the request through.
|
||||
*
|
||||
*
|
||||
* - If authentication succeeds it sets `req.sessionId` and `req.user`
|
||||
* - `req.user` is the user that made the request and it's used in
|
||||
* all Wasp features that need to know the user that made the request.
|
||||
@ -16,21 +16,23 @@ import { throwInvalidCredentialsError } from 'wasp/auth/utils'
|
||||
*/
|
||||
const auth = handleRejection(async (req, res, next) => {
|
||||
const authHeader = req.get('Authorization')
|
||||
// NOTE(matija): for now we let tokenless requests through and make it operation's
|
||||
// responsibility to verify whether the request is authenticated or not. In the future
|
||||
// we will develop our own system at Wasp-level for that.
|
||||
if (!authHeader) {
|
||||
// NOTE(matija): for now we let tokenless requests through and make it operation's
|
||||
// responsibility to verify whether the request is authenticated or not. In the future
|
||||
// we will develop our own system at Wasp-level for that.
|
||||
req.sessionId = null
|
||||
req.user = null
|
||||
return next()
|
||||
}
|
||||
|
||||
const { session, user } = await getSessionAndUserFromBearerToken(req);
|
||||
const sessionAndUser = await getSessionAndUserFromBearerToken(req)
|
||||
|
||||
if (!session || !user) {
|
||||
if (sessionAndUser === null) {
|
||||
throwInvalidCredentialsError()
|
||||
}
|
||||
|
||||
req.sessionId = session.id
|
||||
req.user = user
|
||||
req.sessionId = sessionAndUser.session.id
|
||||
req.user = sessionAndUser.user
|
||||
|
||||
next()
|
||||
})
|
||||
|
@ -22,6 +22,8 @@
|
||||
{=! Used by our code, uncodumented (but accessible) for users. =}
|
||||
"./auth/session": "./dist/auth/session.js",
|
||||
{=! Used by our code, uncodumented (but accessible) for users. =}
|
||||
"./auth/user": "./dist/auth/user.js",
|
||||
{=! Used by our code, uncodumented (but accessible) for users. =}
|
||||
"./auth/providers/types": "./dist/auth/providers/types.js",
|
||||
{=! Used by our code, uncodumented (but accessible) for users. =}
|
||||
"./auth/types": "./dist/auth/types.js",
|
||||
|
@ -9,23 +9,27 @@ import {
|
||||
deserializeAndSanitizeProviderData
|
||||
} from '../../auth/utils.js'
|
||||
import { type ProviderName } from '../_types/index.js'
|
||||
import { getFirstProviderUserId } from '../../auth/user.js'
|
||||
import { Expand } from '../../universal/types.js'
|
||||
|
||||
// PUBLIC API
|
||||
export type AuthUser = AuthUserData & {
|
||||
getFirstProviderUserId: () => string | null,
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
/**
|
||||
* Ideally, we'd do something like this:
|
||||
* ```
|
||||
* export type AuthUser = ReturnType<typeof createAuthUser>
|
||||
* export type AuthUserData = ReturnType<typeof createAuthUserData>
|
||||
* ```
|
||||
* to get the benefits of the createAuthUser and the AuthUser type being in sync.
|
||||
* to get the benefits of the createAuthUser and the AuthUserData type being in sync.
|
||||
*
|
||||
* But since we are not using strict mode, the inferred return type of createAuthUser
|
||||
* is not correct. So we have to define the AuthUser type manually.
|
||||
* is not correct. So we have to define the AuthUserData type manually.
|
||||
*
|
||||
* TODO: Change this once/if we switch to strict mode. https://github.com/wasp-lang/wasp/issues/1938
|
||||
*/
|
||||
export type AuthUser = Omit<UserEntityWithAuth, '{= authFieldOnUserEntityName =}'> & {
|
||||
export type AuthUserData = Omit<UserEntityWithAuth, '{= authFieldOnUserEntityName =}'> & {
|
||||
identities: {
|
||||
{=# enabledProviders.isEmailAuthEnabled =}
|
||||
email: Expand<UserFacingProviderData<'email'>> | null
|
||||
@ -43,7 +47,6 @@ export type AuthUser = Omit<UserEntityWithAuth, '{= authFieldOnUserEntityName =}
|
||||
github: Expand<UserFacingProviderData<'github'>> | null
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
},
|
||||
getFirstProviderUserId: () => string | null,
|
||||
}
|
||||
|
||||
type UserFacingProviderData<PN extends ProviderName> = {
|
||||
@ -61,7 +64,7 @@ export type AuthEntityWithIdentities = {= authEntityName =} & {
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export function createAuthUser(user: UserEntityWithAuth): AuthUser {
|
||||
export function createAuthUserData(user: UserEntityWithAuth): AuthUserData {
|
||||
const { {= authFieldOnUserEntityName =}, ...rest } = user
|
||||
if (!{= authFieldOnUserEntityName =}) {
|
||||
throw new Error(`🐝 Error: trying to create a user without auth data.
|
||||
@ -87,7 +90,6 @@ This should never happen, but it did which means there is a bug in the code.`)
|
||||
return {
|
||||
...rest,
|
||||
identities,
|
||||
getFirstProviderUserId: () => getFirstProviderUserId(user),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,13 @@
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
|
||||
{=# isAuthEnabled =}
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import { type AuthUserData } from './auth/user.js'
|
||||
{=/ isAuthEnabled =}
|
||||
|
||||
type RequestWithExtraFields = Request & {
|
||||
{=# isAuthEnabled =}
|
||||
user?: AuthUser;
|
||||
sessionId?: string;
|
||||
user: AuthUserData | null;
|
||||
sessionId: string | null;
|
||||
{=/ isAuthEnabled =}
|
||||
}
|
||||
|
||||
|
@ -39,12 +39,12 @@ export interface WaspSocketData {
|
||||
{=/ isAuthEnabled =}
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
// PRIVATE API (framework)
|
||||
export type ServerType = Parameters<WebSocketFn>[0]
|
||||
|
||||
// PRIVATE API
|
||||
// PRIVATE API (sdk)
|
||||
export type ClientToServerEvents = Events[0]
|
||||
// PRIVATE API
|
||||
// PRIVATE API (sdk)
|
||||
export type ServerToClientEvents = Events[1]
|
||||
|
||||
type WebSocketFn = typeof {= userWebSocketFn.importIdentifier =}
|
||||
|
@ -4,13 +4,14 @@ import {
|
||||
serialize as superjsonSerialize,
|
||||
} from 'superjson'
|
||||
import { handleRejection } from 'wasp/server/utils'
|
||||
import { makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
|
||||
export function createOperation (handlerFn) {
|
||||
return handleRejection(async (req, res) => {
|
||||
const args = (req.body && superjsonDeserialize(req.body)) || {}
|
||||
const context = {
|
||||
{=# isAuthEnabled =}
|
||||
user: req.user
|
||||
user: makeAuthUserIfPossible(req.user),
|
||||
{=/ isAuthEnabled =}
|
||||
}
|
||||
const result = await handlerFn(args, context)
|
||||
|
@ -5,7 +5,7 @@ import { handleRejection } from 'wasp/server/utils'
|
||||
import { MiddlewareConfigFn, globalMiddlewareConfigForExpress } from '../../middleware/index.js'
|
||||
{=# isAuthEnabled =}
|
||||
import auth from 'wasp/core/auth'
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import { type AuthUserData, makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
{=/ isAuthEnabled =}
|
||||
|
||||
{=# apiNamespaces =}
|
||||
@ -45,12 +45,12 @@ router.{= routeMethod =}(
|
||||
{=/ usesAuth =}
|
||||
handleRejection(
|
||||
(
|
||||
req: Parameters<typeof {= importIdentifier =}>[0]{=# usesAuth =} & { user: AuthUser }{=/ usesAuth =},
|
||||
req: Parameters<typeof {= importIdentifier =}>[0]{=# usesAuth =} & { user: AuthUserData | null }{=/ usesAuth =},
|
||||
res: Parameters<typeof {= importIdentifier =}>[1],
|
||||
) => {
|
||||
const context = {
|
||||
{=# usesAuth =}
|
||||
user: req.user,
|
||||
user: makeAuthUserIfPossible(req.user),
|
||||
{=/ usesAuth =}
|
||||
entities: {
|
||||
{=# entities =}
|
||||
|
@ -8,6 +8,7 @@ import { config, prisma } from 'wasp/server'
|
||||
|
||||
{=# isAuthEnabled =}
|
||||
import { getSessionAndUserFromSessionId } from 'wasp/auth/session'
|
||||
import { makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
{=/ isAuthEnabled =}
|
||||
|
||||
{=& userWebSocketFn.importStatement =}
|
||||
@ -42,8 +43,12 @@ async function addUserToSocketDataIfAuthenticated(socket: Socket, next: (err?: E
|
||||
const sessionId = socket.handshake.auth.sessionId
|
||||
if (sessionId) {
|
||||
try {
|
||||
const { user } = await getSessionAndUserFromSessionId(sessionId)
|
||||
socket.data = { ...socket.data, user }
|
||||
const sessionAndUser = await getSessionAndUserFromSessionId(sessionId)
|
||||
const user = sessionAndUser ? makeAuthUserIfPossible(sessionAndUser.user) : null
|
||||
socket.data = {
|
||||
...socket.data,
|
||||
user,
|
||||
}
|
||||
} catch (err) { }
|
||||
}
|
||||
next()
|
||||
|
@ -200,7 +200,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/package.json"
|
||||
],
|
||||
"8df2ebcc130b484aa956ddf0109b23c83fe2221cb41ea35ed5e99817013f36a9"
|
||||
"995eabb6d01fa0da18e4b83c6686003fc6fcc1b69acc6a6e989281b1b678621a"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -445,7 +445,7 @@
|
||||
"file",
|
||||
"server/src/middleware/operations.ts"
|
||||
],
|
||||
"23efbb9c408f8c12bdb77359a48177430b7da636163562d0105560891ac225b9"
|
||||
"c165909021613cb6e389b324632f199d392b093d18109f05ab33b720c429c187"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -34,6 +34,7 @@
|
||||
"./auth/providers/types": "./dist/auth/providers/types.js",
|
||||
"./auth/session": "./dist/auth/session.js",
|
||||
"./auth/types": "./dist/auth/types.js",
|
||||
"./auth/user": "./dist/auth/user.js",
|
||||
"./auth/utils": "./dist/auth/utils.js",
|
||||
"./auth/validation": "./dist/auth/validation.js",
|
||||
"./client": "./dist/client/index.js",
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
serialize as superjsonSerialize,
|
||||
} from 'superjson'
|
||||
import { handleRejection } from 'wasp/server/utils'
|
||||
import { makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
|
||||
export function createOperation (handlerFn) {
|
||||
return handleRejection(async (req, res) => {
|
||||
|
@ -34,6 +34,7 @@
|
||||
"./auth/providers/types": "./dist/auth/providers/types.js",
|
||||
"./auth/session": "./dist/auth/session.js",
|
||||
"./auth/types": "./dist/auth/types.js",
|
||||
"./auth/user": "./dist/auth/user.js",
|
||||
"./auth/utils": "./dist/auth/utils.js",
|
||||
"./auth/validation": "./dist/auth/validation.js",
|
||||
"./client": "./dist/client/index.js",
|
||||
|
@ -200,7 +200,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/package.json"
|
||||
],
|
||||
"8df2ebcc130b484aa956ddf0109b23c83fe2221cb41ea35ed5e99817013f36a9"
|
||||
"995eabb6d01fa0da18e4b83c6686003fc6fcc1b69acc6a6e989281b1b678621a"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -452,7 +452,7 @@
|
||||
"file",
|
||||
"server/src/middleware/operations.ts"
|
||||
],
|
||||
"23efbb9c408f8c12bdb77359a48177430b7da636163562d0105560891ac225b9"
|
||||
"c165909021613cb6e389b324632f199d392b093d18109f05ab33b720c429c187"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -34,6 +34,7 @@
|
||||
"./auth/providers/types": "./dist/auth/providers/types.js",
|
||||
"./auth/session": "./dist/auth/session.js",
|
||||
"./auth/types": "./dist/auth/types.js",
|
||||
"./auth/user": "./dist/auth/user.js",
|
||||
"./auth/utils": "./dist/auth/utils.js",
|
||||
"./auth/validation": "./dist/auth/validation.js",
|
||||
"./client": "./dist/client/index.js",
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
serialize as superjsonSerialize,
|
||||
} from 'superjson'
|
||||
import { handleRejection } from 'wasp/server/utils'
|
||||
import { makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
|
||||
export function createOperation (handlerFn) {
|
||||
return handleRejection(async (req, res) => {
|
||||
|
@ -447,7 +447,7 @@ waspComplexTest/.wasp/out/server/src/queries/mySpecialQuery.ts
|
||||
waspComplexTest/.wasp/out/server/src/routes/apis/index.ts
|
||||
waspComplexTest/.wasp/out/server/src/routes/auth/index.js
|
||||
waspComplexTest/.wasp/out/server/src/routes/auth/logout.ts
|
||||
waspComplexTest/.wasp/out/server/src/routes/auth/me.js
|
||||
waspComplexTest/.wasp/out/server/src/routes/auth/me.ts
|
||||
waspComplexTest/.wasp/out/server/src/routes/crud/index.ts
|
||||
waspComplexTest/.wasp/out/server/src/routes/crud/tasks.ts
|
||||
waspComplexTest/.wasp/out/server/src/routes/index.js
|
||||
|
@ -95,7 +95,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/index.ts"
|
||||
],
|
||||
"a846aace0af3913411b6c77039cb04d37311d40e5e1f978fd960b38409ec9b66"
|
||||
"b33a60e857c396669db1839ca155ff0f376f096ec9bb53ddad88168f9a4ed49a"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -137,7 +137,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/session.ts"
|
||||
],
|
||||
"03f78291fb369497a07de2f5c2181857e80f158f5eb590b6c095e98c95ff9d14"
|
||||
"637798c4cd8c130aad2efa254c89269ca31c14db8c3c2fa3efda8892105e00b7"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -151,14 +151,14 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/useAuth.ts"
|
||||
],
|
||||
"a465fececdb235d98c23ce25215c1942bc186c2131e3bb4595d45d9ef219fbd5"
|
||||
"ebb4fe26cb12321d247c03b136c48742e245de4b6be6886314313498aadf4455"
|
||||
],
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"../out/sdk/wasp/auth/user.ts"
|
||||
],
|
||||
"2b1911715a41f242cf3f4b8e3954db7146736300fa6888ec8090204eef6b0b6e"
|
||||
"43743b68e46a4bed06983596968b8d9cd6536d0eb8fb0cbd17100d7f82fc54cd"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -347,7 +347,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/core/auth.ts"
|
||||
],
|
||||
"489b2f1840c2d44bdf16f6a949f7217c55c5569a931c3ca9f8484571428840e6"
|
||||
"2cbfa9369a7fa606f520c27590e86f9ea907f95d41280a327f7e1848279b108f"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -473,7 +473,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/package.json"
|
||||
],
|
||||
"75c4ac6e306ef6d0e017dc778ba2c5febe9fea168c3ecf03f80c5086c4e35054"
|
||||
"97d1ff8002d0f083eb400939f8a916313ff3a90557d17be065b724a2100e5e9c"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -522,7 +522,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/server/auth/user.ts"
|
||||
],
|
||||
"76a7add6b2c64e66b583f69320e5cd8ee2595cef709b86d9264cb8d63f7b8adf"
|
||||
"6c026b896fe346b8e0ccc188d21f86f17f0d482800a793b590df326821c33a7f"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -711,7 +711,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/server/utils.ts"
|
||||
],
|
||||
"630dd952c1f4e0c388eb8b73d4f2e9db0cc9015cfc4cb6d6d6dacec6fcd4559c"
|
||||
"d2e322edb5e1e8bd922b64adb0d6e4a9a3112413224b4da4b1fa7cc50cc3cebb"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -963,7 +963,7 @@
|
||||
"file",
|
||||
"server/src/middleware/operations.ts"
|
||||
],
|
||||
"de21cabd75600314b3b59f4110f27eb5411444ffe26926f2dfaf043932185cae"
|
||||
"596a0b642427e926b5428cc76192dd4893e88f15b247f6e546f4d24fe29d46a5"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -984,7 +984,7 @@
|
||||
"file",
|
||||
"server/src/routes/apis/index.ts"
|
||||
],
|
||||
"7928725e710e9b7d23ad81ed8d704b314e72fcd731cb17e854ec593ee51f89f5"
|
||||
"cb1743c9c9c4ce903c5c600eed32bc2b225691b5c59017d640c9b87e2b42d3f8"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -1003,7 +1003,7 @@
|
||||
[
|
||||
[
|
||||
"file",
|
||||
"server/src/routes/auth/me.js"
|
||||
"server/src/routes/auth/me.ts"
|
||||
],
|
||||
"aad9eb2527d58a3d8226ed7ae6c15a09eac7bbb74c58816d01b38fc0a67e2c08"
|
||||
],
|
||||
|
@ -6,6 +6,4 @@ export {
|
||||
} from './user.js'
|
||||
|
||||
// PUBLIC API
|
||||
export {
|
||||
type AuthUser,
|
||||
} from '../server/auth/user.js';
|
||||
export { type AuthUser } from '../server/auth/user.js'
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Request as ExpressRequest } from "express";
|
||||
|
||||
import { type User } from "wasp/entities"
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import { type User } from '../entities/index.js';
|
||||
import { type AuthUserData } from '../server/auth/user.js';
|
||||
|
||||
import { auth } from "./lucia.js";
|
||||
import type { Session } from "lucia";
|
||||
import { throwInvalidCredentialsError } from "./utils.js";
|
||||
|
||||
import { prisma } from 'wasp/server';
|
||||
import { createAuthUser } from "../server/auth/user.js";
|
||||
import { createAuthUserData } from "../server/auth/user.js";
|
||||
|
||||
// PRIVATE API
|
||||
// Creates a new session for the `authId` in the database
|
||||
@ -16,52 +16,42 @@ export async function createSession(authId: string): Promise<Session> {
|
||||
return auth.createSession(authId, {});
|
||||
}
|
||||
|
||||
type SessionAndUser = {
|
||||
session: Session;
|
||||
user: AuthUserData;
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<{
|
||||
user: AuthUser | null,
|
||||
session: Session | null,
|
||||
}> {
|
||||
export async function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<SessionAndUser | null> {
|
||||
const authorizationHeader = req.headers["authorization"];
|
||||
|
||||
if (typeof authorizationHeader !== "string") {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
const sessionId = auth.readBearerToken(authorizationHeader);
|
||||
if (!sessionId) {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
return getSessionAndUserFromSessionId(sessionId);
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export async function getSessionAndUserFromSessionId(sessionId: string): Promise<{
|
||||
user: AuthUser | null,
|
||||
session: Session | null,
|
||||
}> {
|
||||
export async function getSessionAndUserFromSessionId(sessionId: string): Promise<SessionAndUser | null> {
|
||||
const { session, user: authEntity } = await auth.validateSession(sessionId);
|
||||
|
||||
if (!session || !authEntity) {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
session,
|
||||
user: await getUser(authEntity.userId)
|
||||
user: await getAuthUserData(authEntity.userId)
|
||||
}
|
||||
}
|
||||
|
||||
async function getUser(userId: User['id']): Promise<AuthUser> {
|
||||
async function getAuthUserData(userId: User['id']): Promise<AuthUserData> {
|
||||
const user = await prisma.user
|
||||
.findUnique({
|
||||
where: { id: userId },
|
||||
@ -78,7 +68,7 @@ async function getUser(userId: User['id']): Promise<AuthUser> {
|
||||
throwInvalidCredentialsError()
|
||||
}
|
||||
|
||||
return createAuthUser(user);
|
||||
return createAuthUserData(user);
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
|
@ -3,7 +3,8 @@ import { useQuery, buildAndRegisterQuery } from 'wasp/client/operations'
|
||||
import type { QueryFunction, Query } from 'wasp/client/operations/rpc'
|
||||
import { api, handleApiError } from 'wasp/client/api'
|
||||
import { HttpMethod } from 'wasp/client'
|
||||
import type { AuthUser } from '../server/auth/user.js'
|
||||
import type { AuthUser, AuthUserData } from '../server/auth/user.js'
|
||||
import { makeAuthUserIfPossible } from '../auth/user.js'
|
||||
import { UseQueryResult } from '@tanstack/react-query'
|
||||
|
||||
// PUBLIC API
|
||||
@ -19,8 +20,9 @@ function createUserGetter(): Query<void, AuthUser | null> {
|
||||
const getMeRoute = { method: HttpMethod.Get, path: `/${getMeRelativePath}` }
|
||||
const getMe: QueryFunction<void, AuthUser | null> = async () => {
|
||||
try {
|
||||
const response = await api.get(getMeRoute.path)
|
||||
return superjsonDeserialize(response.data)
|
||||
const response = await api.get(getMeRoute.path)
|
||||
const userData = superjsonDeserialize<AuthUserData | null>(response.data)
|
||||
return makeAuthUserIfPossible(userData)
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
return null
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { type AuthIdentity } from '../entities/index.js'
|
||||
import { type ProviderName } from '../server/_types/index.js'
|
||||
import type { AuthUserData, AuthUser } from '../server/auth/user.js'
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
@ -25,6 +26,28 @@ export function getFirstProviderUserId(user?: UserEntityWithAuth): string | null
|
||||
return user.auth.identities[0].providerUserId ?? null;
|
||||
}
|
||||
|
||||
// PRIVATE API (used in SDK and server)
|
||||
export type { AuthUserData, AuthUser } from '../server/auth/user.js'
|
||||
|
||||
// PRIVATE API (used in SDK and server)
|
||||
export function makeAuthUserIfPossible(user: null): null
|
||||
export function makeAuthUserIfPossible(user: AuthUserData): AuthUser
|
||||
export function makeAuthUserIfPossible(
|
||||
user: AuthUserData | null,
|
||||
): AuthUser | null {
|
||||
return user ? makeAuthUser(user) : null
|
||||
}
|
||||
|
||||
function makeAuthUser(data: AuthUserData): AuthUser {
|
||||
return {
|
||||
...data,
|
||||
getFirstProviderUserId: () => {
|
||||
const identities = Object.values(data.identities).filter(Boolean);
|
||||
return identities.length > 0 ? identities[0].id : null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function findUserIdentity(user: UserEntityWithAuth, providerName: ProviderName): AuthIdentity | null {
|
||||
if (!user.auth) {
|
||||
return null;
|
||||
|
@ -4,10 +4,10 @@ import { throwInvalidCredentialsError } from 'wasp/auth/utils'
|
||||
|
||||
/**
|
||||
* Auth middleware
|
||||
*
|
||||
*
|
||||
* If the request includes an `Authorization` header it will try to authenticate the request,
|
||||
* otherwise it will let the request through.
|
||||
*
|
||||
*
|
||||
* - If authentication succeeds it sets `req.sessionId` and `req.user`
|
||||
* - `req.user` is the user that made the request and it's used in
|
||||
* all Wasp features that need to know the user that made the request.
|
||||
@ -16,21 +16,23 @@ import { throwInvalidCredentialsError } from 'wasp/auth/utils'
|
||||
*/
|
||||
const auth = handleRejection(async (req, res, next) => {
|
||||
const authHeader = req.get('Authorization')
|
||||
// NOTE(matija): for now we let tokenless requests through and make it operation's
|
||||
// responsibility to verify whether the request is authenticated or not. In the future
|
||||
// we will develop our own system at Wasp-level for that.
|
||||
if (!authHeader) {
|
||||
// NOTE(matija): for now we let tokenless requests through and make it operation's
|
||||
// responsibility to verify whether the request is authenticated or not. In the future
|
||||
// we will develop our own system at Wasp-level for that.
|
||||
req.sessionId = null
|
||||
req.user = null
|
||||
return next()
|
||||
}
|
||||
|
||||
const { session, user } = await getSessionAndUserFromBearerToken(req);
|
||||
const sessionAndUser = await getSessionAndUserFromBearerToken(req)
|
||||
|
||||
if (!session || !user) {
|
||||
if (sessionAndUser === null) {
|
||||
throwInvalidCredentialsError()
|
||||
}
|
||||
|
||||
req.sessionId = session.id
|
||||
req.user = user
|
||||
req.sessionId = sessionAndUser.session.id
|
||||
req.user = sessionAndUser.user
|
||||
|
||||
next()
|
||||
})
|
||||
|
@ -1,2 +1,2 @@
|
||||
export { getEmail, getUsername, getFirstProviderUserId, } from './user.js';
|
||||
export { type AuthUser, } from '../server/auth/user.js';
|
||||
export { type AuthUser } from '../server/auth/user.js';
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { Request as ExpressRequest } from "express";
|
||||
import { type AuthUser } from 'wasp/auth';
|
||||
import { type AuthUserData } from '../server/auth/user.js';
|
||||
import type { Session } from "lucia";
|
||||
export declare function createSession(authId: string): Promise<Session>;
|
||||
export declare function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<{
|
||||
user: AuthUser | null;
|
||||
session: Session | null;
|
||||
}>;
|
||||
export declare function getSessionAndUserFromSessionId(sessionId: string): Promise<{
|
||||
user: AuthUser | null;
|
||||
session: Session | null;
|
||||
}>;
|
||||
type SessionAndUser = {
|
||||
session: Session;
|
||||
user: AuthUserData;
|
||||
};
|
||||
export declare function getSessionAndUserFromBearerToken(req: ExpressRequest): Promise<SessionAndUser | null>;
|
||||
export declare function getSessionAndUserFromSessionId(sessionId: string): Promise<SessionAndUser | null>;
|
||||
export declare function invalidateSession(sessionId: string): Promise<void>;
|
||||
export {};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { auth } from "./lucia.js";
|
||||
import { throwInvalidCredentialsError } from "./utils.js";
|
||||
import { prisma } from 'wasp/server';
|
||||
import { createAuthUser } from "../server/auth/user.js";
|
||||
import { createAuthUserData } from "../server/auth/user.js";
|
||||
// PRIVATE API
|
||||
// Creates a new session for the `authId` in the database
|
||||
export async function createSession(authId) {
|
||||
@ -11,17 +11,11 @@ export async function createSession(authId) {
|
||||
export async function getSessionAndUserFromBearerToken(req) {
|
||||
const authorizationHeader = req.headers["authorization"];
|
||||
if (typeof authorizationHeader !== "string") {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
const sessionId = auth.readBearerToken(authorizationHeader);
|
||||
if (!sessionId) {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
return getSessionAndUserFromSessionId(sessionId);
|
||||
}
|
||||
@ -29,17 +23,14 @@ export async function getSessionAndUserFromBearerToken(req) {
|
||||
export async function getSessionAndUserFromSessionId(sessionId) {
|
||||
const { session, user: authEntity } = await auth.validateSession(sessionId);
|
||||
if (!session || !authEntity) {
|
||||
return {
|
||||
user: null,
|
||||
session: null,
|
||||
};
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
session,
|
||||
user: await getUser(authEntity.userId)
|
||||
user: await getAuthUserData(authEntity.userId)
|
||||
};
|
||||
}
|
||||
async function getUser(userId) {
|
||||
async function getAuthUserData(userId) {
|
||||
const user = await prisma.user
|
||||
.findUnique({
|
||||
where: { id: userId },
|
||||
@ -54,7 +45,7 @@ async function getUser(userId) {
|
||||
if (!user) {
|
||||
throwInvalidCredentialsError();
|
||||
}
|
||||
return createAuthUser(user);
|
||||
return createAuthUserData(user);
|
||||
}
|
||||
// PRIVATE API
|
||||
export function invalidateSession(sessionId) {
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../auth/session.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAE1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,cAAc;AACd,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc;IAChD,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,gCAAgC,CAAC,GAAmB;IAIxE,MAAM,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAEzD,IAAI,OAAO,mBAAmB,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,mBAAmB,CAAC,CAAC;IAC5D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,OAAO,8BAA8B,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,SAAiB;IAIpE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAE5E,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;QAC5B,OAAO;YACL,IAAI,EAAE,IAAI;YACV,OAAO,EAAE,IAAI;SACd,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO;QACP,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC;KACvC,CAAA;AACH,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,MAAkB;IACvC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI;SAC3B,UAAU,CAAC;QACV,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QACrB,OAAO,EAAE;YACP,IAAI,EAAE;gBACJ,OAAO,EAAE;oBACP,UAAU,EAAE,IAAI;iBACjB;aACF;SACF;KACF,CAAC,CAAA;IAEJ,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,4BAA4B,EAAE,CAAA;IAChC,CAAC;IAED,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,cAAc;AACd,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC3C,CAAC"}
|
||||
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../auth/session.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,OAAO,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAE1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,cAAc;AACd,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc;IAChD,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAOD,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,gCAAgC,CAAC,GAAmB;IACxE,MAAM,mBAAmB,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAEzD,IAAI,OAAO,mBAAmB,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,mBAAmB,CAAC,CAAC;IAC5D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,8BAA8B,CAAC,SAAS,CAAC,CAAC;AACnD,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,SAAiB;IACpE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAE5E,IAAI,CAAC,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,OAAO;QACP,IAAI,EAAE,MAAM,eAAe,CAAC,UAAU,CAAC,MAAM,CAAC;KAC/C,CAAA;AACH,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,MAAkB;IAC/C,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI;SAC3B,UAAU,CAAC;QACV,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QACrB,OAAO,EAAE;YACP,IAAI,EAAE;gBACJ,OAAO,EAAE;oBACP,UAAU,EAAE,IAAI;iBACjB;aACF;SACF;KACF,CAAC,CAAA;IAEJ,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,4BAA4B,EAAE,CAAA;IAChC,CAAC;IAED,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,cAAc;AACd,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC3C,CAAC"}
|
@ -2,6 +2,7 @@ import { deserialize as superjsonDeserialize } from 'superjson';
|
||||
import { useQuery, buildAndRegisterQuery } from 'wasp/client/operations';
|
||||
import { api, handleApiError } from 'wasp/client/api';
|
||||
import { HttpMethod } from 'wasp/client';
|
||||
import { makeAuthUserIfPossible } from '../auth/user.js';
|
||||
// PUBLIC API
|
||||
export const getMe = createUserGetter();
|
||||
// PUBLIC API
|
||||
@ -15,7 +16,8 @@ function createUserGetter() {
|
||||
var _a;
|
||||
try {
|
||||
const response = await api.get(getMeRoute.path);
|
||||
return superjsonDeserialize(response.data);
|
||||
const userData = superjsonDeserialize(response.data);
|
||||
return makeAuthUserIfPossible(userData);
|
||||
}
|
||||
catch (error) {
|
||||
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"useAuth.js","sourceRoot":"","sources":["../../auth/useAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAC/D,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAExE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAIxC,aAAa;AACb,MAAM,CAAC,MAAM,KAAK,GAAiC,gBAAgB,EAAE,CAAA;AAErE,aAAa;AACb,MAAM,CAAC,OAAO,UAAU,OAAO;IAC7B,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAA;AACxB,CAAC;AAED,SAAS,gBAAgB;IACvB,MAAM,iBAAiB,GAAG,SAAS,CAAA;IACnC,MAAM,UAAU,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,iBAAiB,EAAE,EAAE,CAAA;IAC5E,MAAM,KAAK,GAAyC,KAAK,IAAI,EAAE;;QAC7D,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YAC/C,OAAO,oBAAoB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAA,MAAA,KAAK,CAAC,QAAQ,0CAAE,MAAM,MAAK,GAAG,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAA;YACb,CAAC;iBAAM,CAAC;gBACN,cAAc,CAAC,KAAK,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IAED,OAAO,qBAAqB,CAAC,KAAK,EAAE;QAClC,aAAa,EAAE,CAAC,iBAAiB,CAAC;QAClC,UAAU,EAAE,UAAU;QACtB,YAAY,EAAE,CAAC,MAAM,CAAC;KACvB,CAAC,CAAA;AACJ,CAAC"}
|
||||
{"version":3,"file":"useAuth.js","sourceRoot":"","sources":["../../auth/useAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,oBAAoB,EAAE,MAAM,WAAW,CAAA;AAC/D,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAExE,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAGxD,aAAa;AACb,MAAM,CAAC,MAAM,KAAK,GAAiC,gBAAgB,EAAE,CAAA;AAErE,aAAa;AACb,MAAM,CAAC,OAAO,UAAU,OAAO;IAC7B,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAA;AACxB,CAAC;AAED,SAAS,gBAAgB;IACvB,MAAM,iBAAiB,GAAG,SAAS,CAAA;IACnC,MAAM,UAAU,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,iBAAiB,EAAE,EAAE,CAAA;IAC5E,MAAM,KAAK,GAAyC,KAAK,IAAI,EAAE;;QAC7D,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YAC/C,MAAM,QAAQ,GAAG,oBAAoB,CAAsB,QAAQ,CAAC,IAAI,CAAC,CAAA;YACzE,OAAO,sBAAsB,CAAC,QAAQ,CAAC,CAAA;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAA,MAAA,KAAK,CAAC,QAAQ,0CAAE,MAAM,MAAK,GAAG,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAA;YACb,CAAC;iBAAM,CAAC;gBACN,cAAc,CAAC,KAAK,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;IACH,CAAC,CAAA;IAED,OAAO,qBAAqB,CAAC,KAAK,EAAE;QAClC,aAAa,EAAE,CAAC,iBAAiB,CAAC;QAClC,UAAU,EAAE,UAAU;QACtB,YAAY,EAAE,CAAC,MAAM,CAAC;KACvB,CAAC,CAAA;AACJ,CAAC"}
|
@ -1,3 +1,4 @@
|
||||
import type { AuthUserData, AuthUser } from '../server/auth/user.js';
|
||||
/**
|
||||
* We split the user.ts code into two files to avoid some server-only
|
||||
* code (Oslo's hashing functions) being imported on the client.
|
||||
@ -6,3 +7,6 @@ import { type UserEntityWithAuth } from '../server/auth/user.js';
|
||||
export declare function getEmail(user: UserEntityWithAuth): string | null;
|
||||
export declare function getUsername(user: UserEntityWithAuth): string | null;
|
||||
export declare function getFirstProviderUserId(user?: UserEntityWithAuth): string | null;
|
||||
export type { AuthUserData, AuthUser } from '../server/auth/user.js';
|
||||
export declare function makeAuthUserIfPossible(user: null): null;
|
||||
export declare function makeAuthUserIfPossible(user: AuthUserData): AuthUser;
|
||||
|
@ -16,6 +16,15 @@ export function getFirstProviderUserId(user) {
|
||||
}
|
||||
return (_a = user.auth.identities[0].providerUserId) !== null && _a !== void 0 ? _a : null;
|
||||
}
|
||||
export function makeAuthUserIfPossible(user) {
|
||||
return user ? makeAuthUser(user) : null;
|
||||
}
|
||||
function makeAuthUser(data) {
|
||||
return Object.assign(Object.assign({}, data), { getFirstProviderUserId: () => {
|
||||
const identities = Object.values(data.identities).filter(Boolean);
|
||||
return identities.length > 0 ? identities[0].id : null;
|
||||
} });
|
||||
}
|
||||
function findUserIdentity(user, providerName) {
|
||||
var _a;
|
||||
if (!user.auth) {
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../auth/user.ts"],"names":[],"mappings":"AAQA,aAAa;AACb,MAAM,UAAU,QAAQ,CAAC,IAAwB;;IAC/C,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACjE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,WAAW,CAAC,IAAwB;;IAClD,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACpE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,sBAAsB,CAAC,IAAyB;;IAC9D,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,mCAAI,IAAI,CAAC;AACxD,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAwB,EAAE,YAA0B;;IAC5E,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAC9B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,KAAK,YAAY,CACrD,mCAAI,IAAI,CAAC;AACZ,CAAC"}
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../auth/user.ts"],"names":[],"mappings":"AASA,aAAa;AACb,MAAM,UAAU,QAAQ,CAAC,IAAwB;;IAC/C,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACjE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,WAAW,CAAC,IAAwB;;IAClD,OAAO,MAAA,MAAA,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,0CAAE,cAAc,mCAAI,IAAI,CAAC;AACpE,CAAC;AAED,aAAa;AACb,MAAM,UAAU,sBAAsB,CAAC,IAAyB;;IAC9D,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,cAAc,mCAAI,IAAI,CAAC;AACxD,CAAC;AAQD,MAAM,UAAU,sBAAsB,CACpC,IAAyB;IAEzB,OAAO,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACzC,CAAC;AAED,SAAS,YAAY,CAAC,IAAkB;IACtC,uCACK,IAAI,KACP,sBAAsB,EAAE,GAAG,EAAE;YAC3B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAClE,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACzD,CAAC,IACD;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAwB,EAAE,YAA0B;;IAC5E,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAA,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAC9B,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,KAAK,YAAY,CACrD,mCAAI,IAAI,CAAC;AACZ,CAAC"}
|
@ -15,18 +15,20 @@ import { throwInvalidCredentialsError } from 'wasp/auth/utils';
|
||||
*/
|
||||
const auth = handleRejection(async (req, res, next) => {
|
||||
const authHeader = req.get('Authorization');
|
||||
// NOTE(matija): for now we let tokenless requests through and make it operation's
|
||||
// responsibility to verify whether the request is authenticated or not. In the future
|
||||
// we will develop our own system at Wasp-level for that.
|
||||
if (!authHeader) {
|
||||
// NOTE(matija): for now we let tokenless requests through and make it operation's
|
||||
// responsibility to verify whether the request is authenticated or not. In the future
|
||||
// we will develop our own system at Wasp-level for that.
|
||||
req.sessionId = null;
|
||||
req.user = null;
|
||||
return next();
|
||||
}
|
||||
const { session, user } = await getSessionAndUserFromBearerToken(req);
|
||||
if (!session || !user) {
|
||||
const sessionAndUser = await getSessionAndUserFromBearerToken(req);
|
||||
if (sessionAndUser === null) {
|
||||
throwInvalidCredentialsError();
|
||||
}
|
||||
req.sessionId = session.id;
|
||||
req.user = user;
|
||||
req.sessionId = sessionAndUser.session.id;
|
||||
req.user = sessionAndUser.user;
|
||||
next();
|
||||
});
|
||||
export default auth;
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../core/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,gCAAgC,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAA;AAE9D;;;;;;;;;;;GAWG;AACH,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IACpD,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,kFAAkF;QAClF,sFAAsF;QACtF,yDAAyD;QACzD,OAAO,IAAI,EAAE,CAAA;IACf,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,gCAAgC,CAAC,GAAG,CAAC,CAAC;IAEtE,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,4BAA4B,EAAE,CAAA;IAChC,CAAC;IAED,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,EAAE,CAAA;IAC1B,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;IAEf,IAAI,EAAE,CAAA;AACR,CAAC,CAAC,CAAA;AAEF,eAAe,IAAI,CAAA"}
|
||||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../core/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,gCAAgC,EAAE,MAAM,mBAAmB,CAAA;AACpE,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAA;AAE9D;;;;;;;;;;;GAWG;AACH,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IACpD,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IAC3C,kFAAkF;IAClF,sFAAsF;IACtF,yDAAyD;IACzD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,GAAG,CAAC,SAAS,GAAG,IAAI,CAAA;QACpB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAA;QACf,OAAO,IAAI,EAAE,CAAA;IACf,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,gCAAgC,CAAC,GAAG,CAAC,CAAA;IAElE,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAC5B,4BAA4B,EAAE,CAAA;IAChC,CAAC;IAED,GAAG,CAAC,SAAS,GAAG,cAAc,CAAC,OAAO,CAAC,EAAE,CAAA;IACzC,GAAG,CAAC,IAAI,GAAG,cAAc,CAAC,IAAI,CAAA;IAE9B,IAAI,EAAE,CAAA;AACR,CAAC,CAAC,CAAA;AAEF,eAAe,IAAI,CAAA"}
|
@ -2,23 +2,25 @@ import { type User, type Auth, type AuthIdentity } from '../../entities/index.js
|
||||
import { type PossibleProviderData } from '../../auth/utils.js';
|
||||
import { type ProviderName } from '../_types/index.js';
|
||||
import { Expand } from '../../universal/types.js';
|
||||
export type AuthUser = AuthUserData & {
|
||||
getFirstProviderUserId: () => string | null;
|
||||
};
|
||||
/**
|
||||
* Ideally, we'd do something like this:
|
||||
* ```
|
||||
* export type AuthUser = ReturnType<typeof createAuthUser>
|
||||
* export type AuthUserData = ReturnType<typeof createAuthUserData>
|
||||
* ```
|
||||
* to get the benefits of the createAuthUser and the AuthUser type being in sync.
|
||||
* to get the benefits of the createAuthUser and the AuthUserData type being in sync.
|
||||
*
|
||||
* But since we are not using strict mode, the inferred return type of createAuthUser
|
||||
* is not correct. So we have to define the AuthUser type manually.
|
||||
* is not correct. So we have to define the AuthUserData type manually.
|
||||
*
|
||||
* TODO: Change this once/if we switch to strict mode. https://github.com/wasp-lang/wasp/issues/1938
|
||||
*/
|
||||
export type AuthUser = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
export type AuthUserData = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
identities: {
|
||||
google: Expand<UserFacingProviderData<'google'>> | null;
|
||||
};
|
||||
getFirstProviderUserId: () => string | null;
|
||||
};
|
||||
type UserFacingProviderData<PN extends ProviderName> = {
|
||||
id: string;
|
||||
@ -29,5 +31,5 @@ export type UserEntityWithAuth = User & {
|
||||
export type AuthEntityWithIdentities = Auth & {
|
||||
identities: AuthIdentity[];
|
||||
};
|
||||
export declare function createAuthUser(user: UserEntityWithAuth): AuthUser;
|
||||
export declare function createAuthUserData(user: UserEntityWithAuth): AuthUserData;
|
||||
export {};
|
||||
|
@ -10,9 +10,8 @@ var __rest = (this && this.__rest) || function (s, e) {
|
||||
return t;
|
||||
};
|
||||
import { deserializeAndSanitizeProviderData } from '../../auth/utils.js';
|
||||
import { getFirstProviderUserId } from '../../auth/user.js';
|
||||
// PRIVATE API
|
||||
export function createAuthUser(user) {
|
||||
export function createAuthUserData(user) {
|
||||
const { auth } = user, rest = __rest(user, ["auth"]);
|
||||
if (!auth) {
|
||||
throw new Error(`🐝 Error: trying to create a user without auth data.
|
||||
@ -21,7 +20,7 @@ This should never happen, but it did which means there is a bug in the code.`);
|
||||
const identities = {
|
||||
google: getProviderInfo(auth, 'google'),
|
||||
};
|
||||
return Object.assign(Object.assign({}, rest), { identities, getFirstProviderUserId: () => getFirstProviderUserId(user) });
|
||||
return Object.assign(Object.assign({}, rest), { identities });
|
||||
}
|
||||
function getProviderInfo(auth, providerName) {
|
||||
const identity = getIdentity(auth, providerName);
|
||||
|
@ -1 +1 @@
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../../server/auth/user.ts"],"names":[],"mappings":";;;;;;;;;;;AAKA,OAAO,EAEL,kCAAkC,EACnC,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAqC3D,cAAc;AACd,MAAM,UAAU,cAAc,CAAC,IAAwB;IACrD,MAAM,EAAE,IAAI,KAAc,IAAI,EAAb,IAAI,UAAK,IAAI,EAAxB,QAAiB,CAAO,CAAA;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC;6EACyD,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,UAAU,GAAG;QACjB,MAAM,EAAE,eAAe,CAAW,IAAI,EAAE,QAAQ,CAAC;KAClD,CAAA;IACD,uCACK,IAAI,KACP,UAAU,EACV,sBAAsB,EAAE,GAAG,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAC3D;AACH,CAAC;AAED,SAAS,eAAe,CACtB,IAA8B,EAC9B,YAAgB;IAIhB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAA;IACb,CAAC;IACD,uCACK,kCAAkC,CAAK,QAAQ,CAAC,YAAY,EAAE;QAC/D,yBAAyB,EAAE,IAAI;KAChC,CAAC,KACF,EAAE,EAAE,QAAQ,CAAC,cAAc,IAC5B;AACH,CAAC;AAED,SAAS,WAAW,CAClB,IAA8B,EAC9B,YAA0B;;IAE1B,OAAO,MAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,mCAAI,IAAI,CAAA;AAC7E,CAAC"}
|
||||
{"version":3,"file":"user.js","sourceRoot":"","sources":["../../../server/auth/user.ts"],"names":[],"mappings":";;;;;;;;;;;AAKA,OAAO,EAEL,kCAAkC,EACnC,MAAM,qBAAqB,CAAA;AA0C5B,cAAc;AACd,MAAM,UAAU,kBAAkB,CAAC,IAAwB;IACzD,MAAM,EAAE,IAAI,KAAc,IAAI,EAAb,IAAI,UAAK,IAAI,EAAxB,QAAiB,CAAO,CAAA;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC;6EACyD,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,UAAU,GAAG;QACjB,MAAM,EAAE,eAAe,CAAW,IAAI,EAAE,QAAQ,CAAC;KAClD,CAAA;IACD,uCACK,IAAI,KACP,UAAU,IACX;AACH,CAAC;AAED,SAAS,eAAe,CACtB,IAA8B,EAC9B,YAAgB;IAIhB,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAA;IACb,CAAC;IACD,uCACK,kCAAkC,CAAK,QAAQ,CAAC,YAAY,EAAE;QAC/D,yBAAyB,EAAE,IAAI;KAChC,CAAC,KACF,EAAE,EAAE,QAAQ,CAAC,cAAc,IAC5B;AACH,CAAC;AAED,SAAS,WAAW,CAClB,IAA8B,EAC9B,YAA0B;;IAE1B,OAAO,MAAA,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,YAAY,CAAC,mCAAI,IAAI,CAAA;AAC7E,CAAC"}
|
@ -1,8 +1,8 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { type AuthUser } from 'wasp/auth';
|
||||
import { type AuthUserData } from './auth/user.js';
|
||||
type RequestWithExtraFields = Request & {
|
||||
user?: AuthUser;
|
||||
sessionId?: string;
|
||||
user: AuthUserData | null;
|
||||
sessionId: string | null;
|
||||
};
|
||||
/**
|
||||
* Decorator for async express middleware that handles promise rejections.
|
||||
|
@ -40,6 +40,7 @@
|
||||
"./auth/providers/types": "./dist/auth/providers/types.js",
|
||||
"./auth/session": "./dist/auth/session.js",
|
||||
"./auth/types": "./dist/auth/types.js",
|
||||
"./auth/user": "./dist/auth/user.js",
|
||||
"./auth/utils": "./dist/auth/utils.js",
|
||||
"./auth/validation": "./dist/auth/validation.js",
|
||||
"./client": "./dist/client/index.js",
|
||||
|
@ -8,27 +8,30 @@ import {
|
||||
deserializeAndSanitizeProviderData
|
||||
} from '../../auth/utils.js'
|
||||
import { type ProviderName } from '../_types/index.js'
|
||||
import { getFirstProviderUserId } from '../../auth/user.js'
|
||||
import { Expand } from '../../universal/types.js'
|
||||
|
||||
// PUBLIC API
|
||||
export type AuthUser = AuthUserData & {
|
||||
getFirstProviderUserId: () => string | null,
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
/**
|
||||
* Ideally, we'd do something like this:
|
||||
* ```
|
||||
* export type AuthUser = ReturnType<typeof createAuthUser>
|
||||
* export type AuthUserData = ReturnType<typeof createAuthUserData>
|
||||
* ```
|
||||
* to get the benefits of the createAuthUser and the AuthUser type being in sync.
|
||||
* to get the benefits of the createAuthUser and the AuthUserData type being in sync.
|
||||
*
|
||||
* But since we are not using strict mode, the inferred return type of createAuthUser
|
||||
* is not correct. So we have to define the AuthUser type manually.
|
||||
* is not correct. So we have to define the AuthUserData type manually.
|
||||
*
|
||||
* TODO: Change this once/if we switch to strict mode. https://github.com/wasp-lang/wasp/issues/1938
|
||||
*/
|
||||
export type AuthUser = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
export type AuthUserData = Omit<UserEntityWithAuth, 'auth'> & {
|
||||
identities: {
|
||||
google: Expand<UserFacingProviderData<'google'>> | null
|
||||
},
|
||||
getFirstProviderUserId: () => string | null,
|
||||
}
|
||||
|
||||
type UserFacingProviderData<PN extends ProviderName> = {
|
||||
@ -46,7 +49,7 @@ export type AuthEntityWithIdentities = Auth & {
|
||||
}
|
||||
|
||||
// PRIVATE API
|
||||
export function createAuthUser(user: UserEntityWithAuth): AuthUser {
|
||||
export function createAuthUserData(user: UserEntityWithAuth): AuthUserData {
|
||||
const { auth, ...rest } = user
|
||||
if (!auth) {
|
||||
throw new Error(`🐝 Error: trying to create a user without auth data.
|
||||
@ -58,7 +61,6 @@ This should never happen, but it did which means there is a bug in the code.`)
|
||||
return {
|
||||
...rest,
|
||||
identities,
|
||||
getFirstProviderUserId: () => getFirstProviderUserId(user),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Request, Response, NextFunction } from 'express'
|
||||
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import { type AuthUserData } from './auth/user.js'
|
||||
|
||||
type RequestWithExtraFields = Request & {
|
||||
user?: AuthUser;
|
||||
sessionId?: string;
|
||||
user: AuthUserData | null;
|
||||
sessionId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,12 +3,13 @@ import {
|
||||
serialize as superjsonSerialize,
|
||||
} from 'superjson'
|
||||
import { handleRejection } from 'wasp/server/utils'
|
||||
import { makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
|
||||
export function createOperation (handlerFn) {
|
||||
return handleRejection(async (req, res) => {
|
||||
const args = (req.body && superjsonDeserialize(req.body)) || {}
|
||||
const context = {
|
||||
user: req.user
|
||||
user: makeAuthUserIfPossible(req.user),
|
||||
}
|
||||
const result = await handlerFn(args, context)
|
||||
const serializedResult = superjsonSerialize(result)
|
||||
|
@ -3,7 +3,7 @@ import { prisma } from 'wasp/server'
|
||||
import { handleRejection } from 'wasp/server/utils'
|
||||
import { MiddlewareConfigFn, globalMiddlewareConfigForExpress } from '../../middleware/index.js'
|
||||
import auth from 'wasp/core/auth'
|
||||
import { type AuthUser } from 'wasp/auth'
|
||||
import { type AuthUserData, makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
|
||||
import { fooBarNamespaceMiddlewareFn as _waspfooBarNamespacenamespaceMiddlewareConfigFn } from '../../../../../../src/server/apiNamespaces.js'
|
||||
|
||||
@ -25,11 +25,11 @@ router.get(
|
||||
[auth, ...fooBarMiddleware],
|
||||
handleRejection(
|
||||
(
|
||||
req: Parameters<typeof _waspfooBarfn>[0] & { user: AuthUser },
|
||||
req: Parameters<typeof _waspfooBarfn>[0] & { user: AuthUserData | null },
|
||||
res: Parameters<typeof _waspfooBarfn>[1],
|
||||
) => {
|
||||
const context = {
|
||||
user: req.user,
|
||||
user: makeAuthUserIfPossible(req.user),
|
||||
entities: {
|
||||
},
|
||||
}
|
||||
|
@ -207,7 +207,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/package.json"
|
||||
],
|
||||
"a675b7fce46675fe7fc2f5a4edabe8abca4db9cd90a03340d9cf9c54c0e7539f"
|
||||
"77f20065f4a21450ba6d7e146b4fb0c25a5abb1c616dc7a50723c80021ebe13f"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -522,7 +522,7 @@
|
||||
"file",
|
||||
"server/src/middleware/operations.ts"
|
||||
],
|
||||
"23efbb9c408f8c12bdb77359a48177430b7da636163562d0105560891ac225b9"
|
||||
"c165909021613cb6e389b324632f199d392b093d18109f05ab33b720c429c187"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -35,6 +35,7 @@
|
||||
"./auth/providers/types": "./dist/auth/providers/types.js",
|
||||
"./auth/session": "./dist/auth/session.js",
|
||||
"./auth/types": "./dist/auth/types.js",
|
||||
"./auth/user": "./dist/auth/user.js",
|
||||
"./auth/utils": "./dist/auth/utils.js",
|
||||
"./auth/validation": "./dist/auth/validation.js",
|
||||
"./client": "./dist/client/index.js",
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
serialize as superjsonSerialize,
|
||||
} from 'superjson'
|
||||
import { handleRejection } from 'wasp/server/utils'
|
||||
import { makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
|
||||
export function createOperation (handlerFn) {
|
||||
return handleRejection(async (req, res) => {
|
||||
|
@ -200,7 +200,7 @@
|
||||
"file",
|
||||
"../out/sdk/wasp/package.json"
|
||||
],
|
||||
"8df2ebcc130b484aa956ddf0109b23c83fe2221cb41ea35ed5e99817013f36a9"
|
||||
"995eabb6d01fa0da18e4b83c6686003fc6fcc1b69acc6a6e989281b1b678621a"
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -452,7 +452,7 @@
|
||||
"file",
|
||||
"server/src/middleware/operations.ts"
|
||||
],
|
||||
"23efbb9c408f8c12bdb77359a48177430b7da636163562d0105560891ac225b9"
|
||||
"c165909021613cb6e389b324632f199d392b093d18109f05ab33b720c429c187"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -34,6 +34,7 @@
|
||||
"./auth/providers/types": "./dist/auth/providers/types.js",
|
||||
"./auth/session": "./dist/auth/session.js",
|
||||
"./auth/types": "./dist/auth/types.js",
|
||||
"./auth/user": "./dist/auth/user.js",
|
||||
"./auth/utils": "./dist/auth/utils.js",
|
||||
"./auth/validation": "./dist/auth/validation.js",
|
||||
"./client": "./dist/client/index.js",
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
serialize as superjsonSerialize,
|
||||
} from 'superjson'
|
||||
import { handleRejection } from 'wasp/server/utils'
|
||||
import { makeAuthUserIfPossible } from 'wasp/auth/user'
|
||||
|
||||
export function createOperation (handlerFn) {
|
||||
return handleRejection(async (req, res) => {
|
||||
|
@ -26,7 +26,7 @@ export const ProfilePage = ({ user }: { user: AuthUser }) => {
|
||||
}, [])
|
||||
|
||||
useSocketListener('chatMessage', (msg) =>
|
||||
setMessages((priorMessages) => [msg, ...priorMessages])
|
||||
setMessages((priorMessages) => [msg, ...priorMessages]),
|
||||
)
|
||||
|
||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
@ -47,17 +47,18 @@ export const ProfilePage = ({ user }: { user: AuthUser }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Profile page</h2>
|
||||
<h2 className="mt-4 mb-2 font-bold text-xl">User Auth Fields Demo</h2>
|
||||
<div>
|
||||
Hello <strong>{getName(user)}</strong>! Your status is{' '}
|
||||
<strong>
|
||||
{user.identities.email && user.identities.email.isEmailVerified
|
||||
? 'verfied'
|
||||
: 'unverified'}
|
||||
{user.identities.email?.isEmailVerified ? 'verfied' : 'unverified'}
|
||||
</strong>
|
||||
.
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
First provider ID: <strong>{user.getFirstProviderUserId()}</strong>
|
||||
</div>
|
||||
<h2 className="mt-4 mb-2 font-bold text-xl">Links Demo</h2>
|
||||
<Link to="/task/:id" params={{ id: 3 }}>
|
||||
Task 3
|
||||
</Link>
|
||||
@ -70,6 +71,7 @@ export const ProfilePage = ({ user }: { user: AuthUser }) => {
|
||||
})}
|
||||
</p>
|
||||
<div>
|
||||
<h2 className="mt-4 mb-2 font-bold text-xl">WebSockets Demo</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="flex space-x-4 place-items-center">
|
||||
<div>{connectionIcon}</div>
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Link } from "wasp/client/router";
|
||||
import { logout, useAuth } from "wasp/client/auth";
|
||||
import { useQuery, getDate } from "wasp/client/operations";
|
||||
import { Link } from 'wasp/client/router'
|
||||
import { logout, useAuth } from 'wasp/client/auth'
|
||||
import { useQuery, getDate } from 'wasp/client/operations'
|
||||
|
||||
import './Main.css'
|
||||
import { getName } from './user'
|
||||
|
||||
export function App({ children }: any) {
|
||||
export function App({ children }: { children: React.ReactNode }) {
|
||||
const { data: user } = useAuth()
|
||||
const { data: date } = useQuery(getDate)
|
||||
|
||||
|
@ -2,7 +2,6 @@ import React, { useEffect } from 'react'
|
||||
import { type AuthUser as User } from 'wasp/auth'
|
||||
import { Link } from 'wasp/client/router'
|
||||
import { api } from 'wasp/client/api'
|
||||
import { getName } from '../user'
|
||||
|
||||
async function fetchCustomRoute() {
|
||||
const res = await api.get('/foo/bar')
|
||||
@ -14,13 +13,11 @@ export const ProfilePage = ({ user }: { user: User }) => {
|
||||
fetchCustomRoute()
|
||||
}, [])
|
||||
|
||||
const name = getName(user)
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Profile page</h2>
|
||||
<div>
|
||||
Hello <strong>{name}</strong>! Your status is{' '}
|
||||
Hello <strong>{user.getFirstProviderUserId()}</strong>! Your status is{' '}
|
||||
<strong>
|
||||
{user.identities.email && user.identities.email.isEmailVerified
|
||||
? 'verfied'
|
||||
|
@ -1,63 +1,62 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test("has title", async ({ page }) => {
|
||||
await page.goto("http:/localhost:3000");
|
||||
test('has title', async ({ page }) => {
|
||||
await page.goto('/')
|
||||
|
||||
await expect(page).toHaveTitle(/ToDo App/);
|
||||
});
|
||||
test.describe("signup and login", () => {
|
||||
const randomEmail =
|
||||
"test" + Math.random().toString(36).substring(7) + "@test.com";
|
||||
const password = "12345678";
|
||||
await expect(page).toHaveTitle(/ToDo App/)
|
||||
})
|
||||
test.describe('signup and login', () => {
|
||||
const randomEmail = `test${Math.random().toString(36).substring(7)}@test.com`
|
||||
const password = '12345678'
|
||||
|
||||
test.describe.configure({ mode: "serial" });
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test("social button renders", async ({ page }) => {
|
||||
await page.goto("/signup");
|
||||
test('social button renders', async ({ page }) => {
|
||||
await page.goto('/signup')
|
||||
|
||||
await page.waitForSelector("text=Create a new account");
|
||||
await page.waitForSelector('text=Create a new account')
|
||||
|
||||
await expect(
|
||||
page.locator("a[href='http://localhost:3001/auth/google/login']")
|
||||
).toBeVisible();
|
||||
});
|
||||
page.locator("a[href='http://localhost:3001/auth/google/login']"),
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test("can sign up", async ({ page }) => {
|
||||
await page.goto("/signup");
|
||||
test('can sign up', async ({ page }) => {
|
||||
await page.goto('/signup')
|
||||
|
||||
await page.waitForSelector("text=Create a new account");
|
||||
await page.waitForSelector('text=Create a new account')
|
||||
|
||||
await page.locator("input[type='email']").fill(randomEmail);
|
||||
await page.locator("input[type='password']").fill(password);
|
||||
await page.locator("button").click();
|
||||
await page.locator("input[type='email']").fill(randomEmail)
|
||||
await page.locator("input[type='password']").fill(password)
|
||||
await page.locator('button').click()
|
||||
|
||||
await expect(page.locator("body")).toContainText(
|
||||
`You've signed up successfully! Check your email for the confirmation link.`
|
||||
);
|
||||
});
|
||||
await expect(page.locator('body')).toContainText(
|
||||
`You've signed up successfully! Check your email for the confirmation link.`,
|
||||
)
|
||||
})
|
||||
|
||||
test("can log in and create a task", async ({ page }) => {
|
||||
await page.goto("/login");
|
||||
test('can log in and create a task', async ({ page }) => {
|
||||
await page.goto('/login')
|
||||
|
||||
await page.waitForSelector("text=Log in to your account");
|
||||
await page.waitForSelector('text=Log in to your account')
|
||||
|
||||
await page.locator("input[type='email']").fill(randomEmail);
|
||||
await page.locator("input[type='password']").fill("12345678xxx");
|
||||
await page.getByRole("button", { name: "Log in" }).click();
|
||||
await page.locator("input[type='email']").fill(randomEmail)
|
||||
await page.locator("input[type='password']").fill('12345678xxx')
|
||||
await page.getByRole('button', { name: 'Log in' }).click()
|
||||
|
||||
await expect(page.locator("body")).toContainText(`Invalid credentials`);
|
||||
await expect(page.locator('body')).toContainText(`Invalid credentials`)
|
||||
|
||||
await page.locator("input[type='password']").fill(password);
|
||||
await page.getByRole("button", { name: "Log in" }).click();
|
||||
await page.locator("input[type='password']").fill(password)
|
||||
await page.getByRole('button', { name: 'Log in' }).click()
|
||||
|
||||
await expect(page).toHaveURL("/profile");
|
||||
await expect(page).toHaveURL('/profile')
|
||||
|
||||
await page.goto("/");
|
||||
await page.goto('/')
|
||||
|
||||
const randomTask = "New Task " + Math.random().toString(36).substring(7);
|
||||
await page.locator("input[type='text']").fill(randomTask);
|
||||
await page.getByText("Create new task").click();
|
||||
const randomTask = 'New Task ' + Math.random().toString(36).substring(7)
|
||||
await page.locator("input[type='text']").fill(randomTask)
|
||||
await page.getByText('Create new task').click()
|
||||
|
||||
await expect(page.locator("body")).toContainText(randomTask);
|
||||
});
|
||||
});
|
||||
await expect(page.locator('body')).toContainText(randomTask)
|
||||
})
|
||||
})
|
||||
|
39
waspc/headless-test/tests/user-api.spec.ts
Normal file
39
waspc/headless-test/tests/user-api.spec.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
test.describe('user API', () => {
|
||||
const randomEmail = `test${Math.random().toString(36).substring(7)}@test.com`
|
||||
const password = '12345678'
|
||||
|
||||
test.describe.configure({ mode: 'serial' })
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
const page = await browser.newPage()
|
||||
|
||||
// Sign up
|
||||
await page.goto('/signup')
|
||||
|
||||
await page.waitForSelector('text=Create a new account')
|
||||
|
||||
await page.locator("input[type='email']").fill(randomEmail)
|
||||
await page.locator("input[type='password']").fill(password)
|
||||
await page.locator('button').click()
|
||||
})
|
||||
|
||||
test('user API works on the client', async ({ page }) => {
|
||||
await page.goto('/login')
|
||||
|
||||
await page.waitForSelector('text=Log in to your account')
|
||||
|
||||
await page.locator("input[type='email']").fill(randomEmail)
|
||||
await page.locator("input[type='password']").fill(password)
|
||||
await page.getByRole('button', { name: 'Log in' }).click()
|
||||
|
||||
await page.waitForSelector('text=Profile page')
|
||||
|
||||
await expect(page.locator('body')).toContainText(
|
||||
`Hello ${randomEmail}! Your status is verfied`,
|
||||
)
|
||||
|
||||
await expect(page.locator('a[href="/profile"]')).toContainText(randomEmail)
|
||||
})
|
||||
})
|
@ -46,7 +46,7 @@ genAuth spec = case maybeAuth of
|
||||
Just auth ->
|
||||
sequence
|
||||
[ genAuthRoutesIndex auth,
|
||||
genFileCopy [relfile|routes/auth/me.js|],
|
||||
genFileCopy [relfile|routes/auth/me.ts|],
|
||||
genFileCopy [relfile|routes/auth/logout.ts|],
|
||||
genProvidersIndex auth
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user