Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/core/src/@types/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@ export interface SessionStrategy<DefaultUser extends User = User> {
* @unstable This API is experimental and may change in future releases.
*/
signUp(payload: Record<string, unknown>, request: Request): Promise<string>
/**
* Sign in a user with the given credentials and request. Returns the session token on success.
* @unstable This API is experimental and may change in future releases.
*/
signInCredentials(payload: Record<string, unknown>, request: Request, redirectTo?: string): Promise<string>
signIn(
oauth: string,
request: Request,
Expand Down
20 changes: 17 additions & 3 deletions packages/core/src/api/signInCredentials.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AuraAuthError } from "@/shared/errors.ts"
import { getErrorName } from "@/shared/utils.ts"
import { HeadersBuilder } from "@aura-stack/router"
import { secureApiHeaders } from "@/shared/headers.ts"
import { createCSRF, hashPassword, verifyPassword } from "@/shared/crypto.ts"
Expand Down Expand Up @@ -35,7 +36,7 @@ export const signInCredentials = async ({
if (!session) {
throw new AuraAuthError({ code: "AUTH_CREDENTIALS_INVALID" })
}
const sessionToken = await sessionStrategy.createSession(session, request)
const sessionToken = await sessionStrategy.signInCredentials(session, request)
const csrfToken = await createCSRF(ctx.jose)
logger?.log("CREDENTIALS_SIGN_IN_SUCCESS")

Expand Down Expand Up @@ -70,6 +71,9 @@ export const signInCredentials = async ({
"An error occurred during credentials sign-in.",
401
)
const error_type = getErrorName(error)
const error_code = error instanceof AuraAuthError ? error.code : "UNKNOWN_ERROR"
const error_message = error instanceof Error ? error.message : String(error)
const headers = new Headers(secureApiHeaders)
const invalidCredentials: SignInCredentialsAPIReturn = {
success: false,
Expand All @@ -84,13 +88,23 @@ export const signInCredentials = async ({
if (error instanceof AuraAuthError && error.code === "AUTH_CREDENTIALS_INVALID") {
logger?.log("INVALID_CREDENTIALS", {
severity: "warning",
structuredData: { path: "/signIn/credentials" },
structuredData: {
path: "/signIn/credentials",
error_type,
error_code,
error_message,
},
})
return invalidCredentials
}
logger?.log("CREDENTIALS_SIGN_IN_FAILED", {
severity: "error",
structuredData: { path: "/signIn/credentials" },
structuredData: {
path: "/signIn/credentials",
error_type,
error_code,
error_message,
},
})
return invalidCredentials
}
Expand Down
14 changes: 8 additions & 6 deletions packages/core/src/session/stateful/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { revokeSession } from "@/session/stateful/revokeSession.ts"
import { destroySession } from "@/session/stateful/destroySession.ts"
import { refreshSession } from "@/session/stateful/refreshSession.ts"
import { refreshUserInfo } from "@/session/stateful/refreshUserInfo.ts"
import { signInCredentials } from "@/session/stateful/signInCredentials.ts"
import { getProviderTokens } from "@/session/stateful/getProviderTokens.ts"
import { isProviderConnected } from "@/session/stateful/isProviderConnected.ts"
import type { SessionStrategy, User, InternalStatefulContext } from "@/@types/index.ts"
Expand All @@ -16,17 +17,18 @@ export const createStatefulStrategy = <DefaultUser extends User = User>(
ctx: InternalStatefulContext
): SessionStrategy<DefaultUser> => {
return {
refreshUserInfo: refreshUserInfo(ctx),
signUp: signUp(ctx),
signIn: signIn(ctx),
getSession: getSession(ctx),
revokeToken: revokeToken(ctx),
oauthCallback: oauthCallback(ctx),
createSession: createSession(ctx),
refreshSession: refreshSession(ctx),
revokeSession: revokeSession(ctx),
revokeToken: revokeToken(ctx),
destroySession: destroySession(ctx),
refreshSession: refreshSession(ctx),
refreshUserInfo: refreshUserInfo(ctx),
signInCredentials: signInCredentials(ctx),
getProviderTokens: getProviderTokens(ctx),
isProviderConnected: isProviderConnected(ctx),
signIn: signIn(ctx),
oauthCallback: oauthCallback(ctx),
signUp: signUp(ctx),
}
}
96 changes: 96 additions & 0 deletions packages/core/src/session/stateful/signInCredentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { AuraAuthError } from "@/shared/errors.ts"
import { createHash, createSecretValue } from "@/shared/crypto.ts"
import { createDevice as __createDevice } from "@/shared/utils/session-strategy.ts"
import type { InternalStatefulContext } from "@/@types/config.ts"

export const signInCredentials = ({ ctx, cookies, cookieManager }: InternalStatefulContext) => {
const { logger, sessionConfig } = ctx
const createDevice = __createDevice({ ctx, cookies, cookieManager })

return async (payload: Record<string, unknown>, request: Request): Promise<string> => {
logger?.log("STATEFUL_SIGN_IN_CREDENTIALS_START", {
structuredData: {
strategy: "stateful",
operation: "signInCredentials",
},
})

if (ctx.identity.skipValidation) {
logger?.log("IDENTITY_VALIDATION_DISABLED", {
structuredData: {
identity_validation_disabled: true,
},
})
}

const validatedPayload = ctx.identity.skipValidation ? payload : await ctx.identity.schemaRegistry.parse(payload)
logger?.log("STATEFUL_PAYLOAD_VALIDATION", {
structuredData: {
validation_skipped: ctx.identity.skipValidation || false,
has_email: Boolean(validatedPayload?.email) || false,
},
})

const { sub } = validatedPayload

const user = await sessionConfig.adapter.getUserById(sub)
if (!user) {
throw new AuraAuthError({ code: "AUTH_CREDENTIALS_INVALID" })
}

const device = await createDevice(user.id, request)

const secretValue = createSecretValue(64)
logger?.log("STATEFUL_TOKEN_GENERATED", {
structuredData: {
token_length: secretValue.length,
},
})

const tokenHash = await createHash(secretValue)
logger?.log("STATEFUL_TOKEN_HASHED", {
structuredData: {
hash_length: tokenHash.length,
},
})

const expiresAt = new Date(Date.now() + 60 * 60 * 24 * 15 * 1000)
logger?.log("STATEFUL_SESSION_EXPIRATION_SET", {
structuredData: {
expires_at: expiresAt?.toISOString(),
max_age_days: 15,
},
})

const dbSession = await sessionConfig.adapter.createSession({
id: createSecretValue(32),
userId: user.id,
deviceId: device.id,
authenticatedWith: "credentials",
status: "active",
mfaState: "none",
tokenHash,
expiresAt,
metadata: null,
})

logger?.log("STATEFUL_SESSION_CREATED", {
structuredData: {
session_id: dbSession.id,
user_id: dbSession.userId,
status: dbSession.status,
expires_at: dbSession?.expiresAt?.toISOString(),
},
})

logger?.log("STATEFUL_SIGN_IN_CREDENTIALS_SUCCESS", {
structuredData: {
session_id: dbSession.id,
user_id: dbSession.userId,
token_returned: true,
},
})

return tokenHash
Comment thread
halvaradop marked this conversation as resolved.
}
}
18 changes: 10 additions & 8 deletions packages/core/src/session/stateless/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createSession } from "@/session/stateless/createSession.ts"
import { destroySession } from "@/session/stateless/destroySession.ts"
import { refreshSession } from "@/session/stateless/refreshSession.ts"
import { refreshUserInfo } from "@/session/stateless/refreshUserInfo.ts"
import { signInCredentials } from "@/session/stateless/signInCredentials.ts"
import { getProviderTokens } from "@/session/stateless/getProviderTokens.ts"
import { isProviderConnected } from "@/session/stateless/isProviderConnected.ts"
import type { SessionStrategy, User, InternalStatelessContext } from "@/@types/index.ts"
Expand All @@ -21,17 +22,18 @@ export const createStatelessStrategy = <DefaultUser extends User = User>(
}

return {
revokeSession,
signIn: signIn(ctx),
signUp: signUp(ctx),
getSession: getSession(ctx),
revokeToken: revokeToken(ctx),
createSession: createSession(ctx),
getProviderTokens: getProviderTokens(ctx),
oauthCallback: oauthCallback(ctx),
refreshSession: refreshSession(ctx),
revokeSession,
revokeToken: revokeToken(ctx),
isProviderConnected: isProviderConnected(ctx),
refreshUserInfo: refreshUserInfo(ctx),
destroySession: destroySession(ctx),
signIn: signIn(ctx),
oauthCallback: oauthCallback(ctx),
signUp: signUp(ctx),
refreshUserInfo: refreshUserInfo(ctx),
getProviderTokens: getProviderTokens(ctx),
signInCredentials: signInCredentials(ctx),
isProviderConnected: isProviderConnected(ctx),
}
}
10 changes: 10 additions & 0 deletions packages/core/src/session/stateless/signInCredentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createSession as __createSession } from "@/session/stateless/createSession.ts"
import type { InternalStatelessContext, TypedJWTPayload, User } from "@/@types/index.ts"

export const signInCredentials = <DefaultUser extends User = User>(ctx: InternalStatelessContext) => {
const createSession = __createSession<DefaultUser>(ctx)

return async (payload: Record<string, unknown>, _request: Request): Promise<string> => {
return await createSession(payload as TypedJWTPayload<DefaultUser>)
}
}
12 changes: 12 additions & 0 deletions packages/core/src/shared/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,18 @@ export const logMessages = {
msgId: "SIGN_UP_ERROR",
message: "Error occurred during user sign-up process",
},
STATEFUL_SIGN_IN_CREDENTIALS_START: {
facility: 4,
severity: "debug",
msgId: "STATEFUL_SIGN_IN_CREDENTIALS_START",
message: "Starting stateful sign-in with credentials",
},
STATEFUL_SIGN_IN_CREDENTIALS_SUCCESS: {
facility: 4,
severity: "info",
msgId: "STATEFUL_SIGN_IN_CREDENTIALS_SUCCESS",
message: "Stateful sign-in with credentials completed successfully",
},
} as const

export const createLogEntry = <T extends keyof typeof logMessages>(key: T, overrides?: Partial<SyslogOptions>): SyslogOptions => {
Expand Down
Loading
Loading