fix: 加固认证撤销与运行配置
This commit is contained in:
@@ -15,6 +15,21 @@ export interface AccessClaims {
|
||||
iat: number;
|
||||
}
|
||||
const b64 = (v: Buffer | string) => Buffer.from(v).toString("base64url");
|
||||
const BASE64URL = /^[A-Za-z0-9_-]+$/;
|
||||
const UUID =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
const DEVICE_ID = /^[A-Za-z0-9_-]{8,128}$/;
|
||||
const decodeCanonical = (value: string): Buffer => {
|
||||
if (!BASE64URL.test(value)) throw new Error();
|
||||
const decoded = Buffer.from(value, "base64url");
|
||||
if (decoded.toString("base64url") !== value) throw new Error();
|
||||
return decoded;
|
||||
};
|
||||
const plainObject = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value) &&
|
||||
Object.getPrototypeOf(value) === Object.prototype;
|
||||
@Injectable()
|
||||
export class TokenService {
|
||||
issueAccess(
|
||||
@@ -39,28 +54,48 @@ export class TokenService {
|
||||
const parts = token.split(".");
|
||||
if (parts.length !== 3) throw new Error();
|
||||
const [h, p, s] = parts as [string, string, string];
|
||||
const header = JSON.parse(Buffer.from(h, "base64url").toString()) as {
|
||||
alg?: string;
|
||||
};
|
||||
if (header.alg !== "HS256") throw new Error();
|
||||
const header: unknown = JSON.parse(decodeCanonical(h).toString());
|
||||
if (
|
||||
!plainObject(header) ||
|
||||
header.alg !== "HS256" ||
|
||||
header.typ !== "JWT" ||
|
||||
Object.keys(header).length !== 2
|
||||
)
|
||||
throw new Error();
|
||||
const expected = createHmac("sha256", jwtSecret())
|
||||
.update(`${h}.${p}`)
|
||||
.digest();
|
||||
const actual = Buffer.from(s, "base64url");
|
||||
const actual = decodeCanonical(s);
|
||||
if (
|
||||
expected.length !== actual.length ||
|
||||
!timingSafeEqual(expected, actual)
|
||||
)
|
||||
throw new Error();
|
||||
const claims = JSON.parse(
|
||||
Buffer.from(p, "base64url").toString(),
|
||||
) as AccessClaims;
|
||||
const decodedClaims: unknown = JSON.parse(decodeCanonical(p).toString());
|
||||
if (!plainObject(decodedClaims)) throw new Error();
|
||||
const claims = decodedClaims as unknown as AccessClaims;
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (
|
||||
Object.keys(claims).length !== 9 ||
|
||||
!UUID.test(claims.sub) ||
|
||||
!UUID.test(claims.session_id) ||
|
||||
!DEVICE_ID.test(claims.device_id) ||
|
||||
!Array.isArray(claims.scopes) ||
|
||||
claims.scopes.length !== 1 ||
|
||||
claims.scopes[0] !== "user" ||
|
||||
!Number.isInteger(claims.token_version) ||
|
||||
claims.token_version < 0 ||
|
||||
!Number.isFinite(claims.iat) ||
|
||||
!Number.isInteger(claims.iat) ||
|
||||
!Number.isFinite(claims.exp) ||
|
||||
!Number.isInteger(claims.exp) ||
|
||||
claims.iat > now + 300 ||
|
||||
claims.exp <= claims.iat ||
|
||||
claims.iss !== (process.env.JWT_ISSUER ?? "drift-api") ||
|
||||
claims.aud !== (process.env.JWT_AUDIENCE ?? "drift-web")
|
||||
)
|
||||
throw new Error();
|
||||
if (claims.exp <= Math.floor(Date.now() / 1000))
|
||||
if (claims.exp <= now)
|
||||
throw new DomainException(
|
||||
ErrorCode.AUTH_TOKEN_EXPIRED,
|
||||
"Access token expired",
|
||||
|
||||
Reference in New Issue
Block a user