mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-18 10:12:55 +02:00
Bind share link sessions to the token's own resource
This commit is contained in:
@@ -164,6 +164,14 @@ export async function exchangeSession(
|
|||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
if (res) {
|
if (res) {
|
||||||
|
if (res.resourceId !== resource.resourceId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.UNAUTHORIZED,
|
||||||
|
"Invalid request token"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
await createResourceSession({
|
await createResourceSession({
|
||||||
token,
|
token,
|
||||||
resourceId: resource.resourceId,
|
resourceId: resource.resourceId,
|
||||||
|
|||||||
@@ -821,10 +821,6 @@ export async function verifyResourceSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resourceSession.accessTokenId) {
|
if (resourceSession.accessTokenId) {
|
||||||
logger.debug(
|
|
||||||
"Resource allowed because access token session is valid"
|
|
||||||
);
|
|
||||||
|
|
||||||
const [tokenItem] = await db
|
const [tokenItem] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(resourceAccessToken)
|
.from(resourceAccessToken)
|
||||||
@@ -836,26 +832,37 @@ export async function verifyResourceSession(
|
|||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
const userData = tokenItem
|
if (
|
||||||
? await getAccessTokenUserData(
|
tokenItem &&
|
||||||
tokenItem,
|
tokenItem.resourceId === resource.resourceId
|
||||||
resource.orgId
|
) {
|
||||||
)
|
logger.debug(
|
||||||
: undefined;
|
"Resource allowed because access token session is valid"
|
||||||
|
);
|
||||||
|
|
||||||
logAccessTokenRequestAudit(
|
const userData = await getAccessTokenUserData(
|
||||||
{
|
tokenItem,
|
||||||
resourceId: resource.resourceId,
|
resource.orgId
|
||||||
orgId: resource.orgId,
|
);
|
||||||
location: ipCC,
|
|
||||||
accessTokenId: resourceSession.accessTokenId,
|
logAccessTokenRequestAudit(
|
||||||
tokenTitle: tokenItem?.title ?? null,
|
{
|
||||||
userData
|
resourceId: resource.resourceId,
|
||||||
},
|
orgId: resource.orgId,
|
||||||
parsedBody.data
|
location: ipCC,
|
||||||
|
accessTokenId: resourceSession.accessTokenId,
|
||||||
|
tokenTitle: tokenItem.title ?? null,
|
||||||
|
userData
|
||||||
|
},
|
||||||
|
parsedBody.data
|
||||||
|
);
|
||||||
|
|
||||||
|
return allowed(res, userData, dontStripSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"Access token session does not belong to this resource"
|
||||||
);
|
);
|
||||||
|
|
||||||
return allowed(res, userData, dontStripSession);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceSession.userSessionId && sso) {
|
if (resourceSession.userSessionId && sso) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||||
import { db } from "@server/db";
|
import { db, users } from "@server/db";
|
||||||
import { Resource, resources, users } from "@server/db";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
@@ -65,82 +64,35 @@ export async function authWithAccessToken(
|
|||||||
const { accessToken, accessTokenId } = parsedBody.data;
|
const { accessToken, accessTokenId } = parsedBody.data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let valid;
|
const { valid, tokenItem, error, resource } =
|
||||||
let tokenItem;
|
await verifyResourceAccessToken({
|
||||||
let error;
|
accessToken,
|
||||||
let resource: Resource | undefined;
|
|
||||||
|
|
||||||
if (accessTokenId) {
|
|
||||||
if (!resourceId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Resource ID is required"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [foundResource] = await db
|
|
||||||
.select()
|
|
||||||
.from(resources)
|
|
||||||
.where(eq(resources.resourceId, resourceId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!foundResource) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.NOT_FOUND, "Resource not found")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await verifyResourceAccessToken({
|
|
||||||
accessTokenId,
|
accessTokenId,
|
||||||
accessToken
|
resourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
valid = res.valid;
|
if (!valid || !tokenItem || !resource) {
|
||||||
tokenItem = res.tokenItem;
|
if (resource) {
|
||||||
error = res.error;
|
if (config.getRawConfig().app.log_failed_attempts) {
|
||||||
resource = foundResource;
|
logger.info(
|
||||||
} else {
|
`Resource access token invalid. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
|
||||||
const res = await verifyResourceAccessToken({
|
);
|
||||||
accessToken
|
}
|
||||||
});
|
|
||||||
|
|
||||||
valid = res.valid;
|
logAccessAudit({
|
||||||
tokenItem = res.tokenItem;
|
orgId: resource.orgId,
|
||||||
error = res.error;
|
resourceId: resource.resourceId,
|
||||||
resource = res.resource;
|
action: false,
|
||||||
}
|
type: "accessToken",
|
||||||
|
userAgent: req.headers["user-agent"],
|
||||||
if (!tokenItem || !resource) {
|
requestIp: req.ip
|
||||||
return next(
|
});
|
||||||
createHttpError(
|
|
||||||
HttpCode.UNAUTHORIZED,
|
|
||||||
"Access token does not exist for resource"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
if (config.getRawConfig().app.log_failed_attempts) {
|
|
||||||
logger.info(
|
|
||||||
`Resource access token invalid. Resource ID: ${resource.resourceId}. IP: ${req.ip}.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logAccessAudit({
|
|
||||||
orgId: resource.orgId,
|
|
||||||
resourceId: resource.resourceId,
|
|
||||||
action: false,
|
|
||||||
type: "accessToken",
|
|
||||||
userAgent: req.headers["user-agent"],
|
|
||||||
requestIp: req.ip
|
|
||||||
});
|
|
||||||
|
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.UNAUTHORIZED,
|
HttpCode.UNAUTHORIZED,
|
||||||
error || "Invalid access token"
|
error || "Access token does not exist for resource"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,10 +248,7 @@ export default async function ResourceAuthPage(props: {
|
|||||||
if (searchParams.token) {
|
if (searchParams.token) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<AccessToken
|
<AccessToken token={searchParams.token} />
|
||||||
token={searchParams.token}
|
|
||||||
resourceId={authInfo.resourceId}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,9 @@ import { useTranslations } from "next-intl";
|
|||||||
|
|
||||||
type AccessTokenProps = {
|
type AccessTokenProps = {
|
||||||
token: string;
|
token: string;
|
||||||
resourceId?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AccessToken({ token, resourceId }: AccessTokenProps) {
|
export default function AccessToken({ token }: AccessTokenProps) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isValid, setIsValid] = useState(false);
|
const [isValid, setIsValid] = useState(false);
|
||||||
|
|
||||||
@@ -59,13 +58,13 @@ export default function AccessToken({ token, resourceId }: AccessTokenProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkSHA256() {
|
async function check() {
|
||||||
try {
|
try {
|
||||||
const res = await api.post<
|
const res = await api.post<
|
||||||
AxiosResponse<AuthWithAccessTokenResponse>
|
AxiosResponse<AuthWithAccessTokenResponse>
|
||||||
>(`/auth/access-token`, {
|
>(`/auth/access-token`, {
|
||||||
accessToken,
|
accessToken,
|
||||||
accessTokenId
|
accessTokenId: accessTokenId || undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.data.data.session) {
|
if (res.data.data.session) {
|
||||||
@@ -82,35 +81,7 @@ export default function AccessToken({ token, resourceId }: AccessTokenProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function check() {
|
check();
|
||||||
try {
|
|
||||||
const res = await api.post<
|
|
||||||
AxiosResponse<AuthWithAccessTokenResponse>
|
|
||||||
>(`/auth/resource/${resourceId}/access-token`, {
|
|
||||||
accessToken,
|
|
||||||
accessTokenId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.data.data.session) {
|
|
||||||
setIsValid(true);
|
|
||||||
window.location.href = appendRequestToken(
|
|
||||||
res.data.data.redirectUrl!,
|
|
||||||
res.data.data.session
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(t("accessTokenError"), e);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessTokenId) {
|
|
||||||
// no access token id so check the sha256
|
|
||||||
checkSHA256();
|
|
||||||
} else {
|
|
||||||
check();
|
|
||||||
}
|
|
||||||
}, [token]);
|
}, [token]);
|
||||||
|
|
||||||
function renderTitle() {
|
function renderTitle() {
|
||||||
|
|||||||
Reference in New Issue
Block a user