Token type improvement
This commit is contained in:
parent
c6c98d36a0
commit
187172e9e7
@ -2,10 +2,10 @@ import { TokenClaims } from "@common/TokenClaims";
|
||||
import * as jwt from "jsonwebtoken";
|
||||
import { computed, createAtom, IAtom, observable } from "mobx";
|
||||
|
||||
export class Token {
|
||||
export class Token<TClaims extends TokenClaims = TokenClaims> {
|
||||
@observable token: string | null;
|
||||
|
||||
@computed get claims(): TokenClaims | null {
|
||||
@computed get claims(): TClaims | null {
|
||||
if (this.token == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { observable } from "mobx";
|
||||
|
||||
import { Token } from "@client/state/Token";
|
||||
import { AccessToken, RefreshToken } from "@common/TokenClaims";
|
||||
|
||||
const LOCAL_STORAGE_KEY = "TokenStore";
|
||||
|
||||
export class TokenStore {
|
||||
@observable accessToken: Token = new Token();
|
||||
@observable refreshToken: Token = new Token();
|
||||
@observable accessToken: Token<AccessToken> = new Token();
|
||||
@observable refreshToken: Token<RefreshToken> = new Token();
|
||||
|
||||
clear() {
|
||||
this.accessToken.token = null;
|
||||
|
@ -1,5 +1,6 @@
|
||||
.app {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.flex-horizontal-space-between {
|
||||
|
@ -3,8 +3,14 @@ export interface BaseClaims {
|
||||
exp?: number;
|
||||
}
|
||||
|
||||
export interface AccessOrRefreshToken extends BaseClaims {
|
||||
type: "access" | "refresh";
|
||||
export interface AccessToken extends BaseClaims {
|
||||
type: "access";
|
||||
aud: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface RefreshToken extends BaseClaims {
|
||||
type: "refresh";
|
||||
aud: number;
|
||||
name: string;
|
||||
}
|
||||
@ -13,4 +19,4 @@ export interface DeviceRegistrationToken extends BaseClaims {
|
||||
type: "device_reg";
|
||||
}
|
||||
|
||||
export type TokenClaims = AccessOrRefreshToken | DeviceRegistrationToken;
|
||||
export type TokenClaims = AccessToken | RefreshToken | DeviceRegistrationToken;
|
||||
|
@ -4,7 +4,7 @@ import { serialize} from "serializr";
|
||||
import ApiError from "@common/ApiError";
|
||||
import { ErrorCode } from "@common/ErrorCode";
|
||||
import * as schema from "@common/sprinklersRpc/schema";
|
||||
import { AccessOrRefreshToken } from "@common/TokenClaims";
|
||||
import { AccessToken } from "@common/TokenClaims";
|
||||
import { verifyAuthorization } from "@server/express/authentication";
|
||||
import { ServerState } from "@server/state";
|
||||
|
||||
@ -12,7 +12,7 @@ export function devices(state: ServerState) {
|
||||
const router = PromiseRouter();
|
||||
|
||||
router.get("/:deviceId", verifyAuthorization(), async (req, res) => {
|
||||
const token = req.token! as AccessOrRefreshToken;
|
||||
const token = req.token!;
|
||||
const userId = token.aud;
|
||||
const deviceId = req.params.deviceId;
|
||||
const userDevice = await state.database.sprinklersDevices
|
||||
|
@ -10,16 +10,14 @@ import {
|
||||
TokenGrantRequest,
|
||||
TokenGrantResponse,
|
||||
} from "@common/httpApi";
|
||||
import { TokenClaims } from "@common/TokenClaims";
|
||||
import { AccessToken, DeviceRegistrationToken, RefreshToken, TokenClaims } from "@common/TokenClaims";
|
||||
import { User } from "../entities";
|
||||
import { ServerState } from "../state";
|
||||
|
||||
export { TokenClaims };
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
token?: TokenClaims;
|
||||
token?: AccessToken;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,7 +51,9 @@ function signToken(claims: TokenClaims): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyToken(token: string): Promise<TokenClaims> {
|
||||
export function verifyToken<TClaims extends TokenClaims = TokenClaims>(
|
||||
token: string, type?: TClaims["type"],
|
||||
): Promise<TClaims> {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.verify(token, JWT_SECRET, {
|
||||
issuer: ISSUER,
|
||||
@ -67,14 +67,18 @@ export function verifyToken(token: string): Promise<TokenClaims> {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
resolve(decoded as any);
|
||||
const claims: TokenClaims = decoded as any;
|
||||
if (type != null && claims.type !== type) {
|
||||
reject(new ApiError(`Expected a "${type} token, received a "${claims.type}" token`));
|
||||
}
|
||||
resolve(claims as TClaims);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function generateAccessToken(user: User, secret: string): Promise<string> {
|
||||
const access_token_claims: TokenClaims = {
|
||||
const access_token_claims: AccessToken = {
|
||||
iss: ISSUER,
|
||||
aud: user.id,
|
||||
name: user.name,
|
||||
@ -86,7 +90,7 @@ function generateAccessToken(user: User, secret: string): Promise<string> {
|
||||
}
|
||||
|
||||
function generateRefreshToken(user: User, secret: string): Promise<string> {
|
||||
const refresh_token_claims: TokenClaims = {
|
||||
const refresh_token_claims: RefreshToken = {
|
||||
iss: ISSUER,
|
||||
aud: user.id,
|
||||
name: user.name,
|
||||
@ -98,7 +102,7 @@ function generateRefreshToken(user: User, secret: string): Promise<string> {
|
||||
}
|
||||
|
||||
function generateDeviceRegistrationToken(secret: string): Promise<string> {
|
||||
const device_reg_token_claims: TokenClaims = {
|
||||
const device_reg_token_claims: DeviceRegistrationToken = {
|
||||
iss: ISSUER,
|
||||
type: "device_reg",
|
||||
};
|
||||
@ -197,7 +201,7 @@ export function verifyAuthorization(options?: Partial<VerifyAuthorizationOpts>):
|
||||
}
|
||||
const token = matches[1];
|
||||
|
||||
req.token = await verifyToken(token);
|
||||
req.token = await verifyToken<AccessToken>(token, "access");
|
||||
|
||||
if (req.token.type !== opts.type) {
|
||||
throw new ApiError(`Invalid token type "${req.token.type}", must be "${opts.type}"`,
|
||||
|
@ -9,8 +9,9 @@ import * as deviceRequests from "@common/sprinklersRpc/deviceRequests";
|
||||
import * as schema from "@common/sprinklersRpc/schema";
|
||||
import * as ws from "@common/sprinklersRpc/websocketData";
|
||||
import { User } from "@server/entities";
|
||||
import { TokenClaims, verifyToken } from "@server/express/authentication";
|
||||
import { verifyToken } from "@server/express/authentication";
|
||||
import { ServerState } from "@server/state";
|
||||
import { AccessToken } from "@common/TokenClaims";
|
||||
|
||||
// tslint:disable:member-ordering
|
||||
|
||||
@ -78,19 +79,16 @@ export class WebSocketClient {
|
||||
if (!data.accessToken) {
|
||||
throw new ws.RpcError("no token specified", ErrorCode.BadRequest);
|
||||
}
|
||||
let decoded: TokenClaims;
|
||||
let claims: AccessToken;
|
||||
try {
|
||||
decoded = await verifyToken(data.accessToken);
|
||||
claims = await verifyToken<AccessToken>(data.accessToken, "access");
|
||||
} catch (e) {
|
||||
throw new ws.RpcError("invalid token", ErrorCode.BadToken, e);
|
||||
}
|
||||
if (decoded.type !== "access") {
|
||||
throw new ws.RpcError("not an access token", ErrorCode.BadToken);
|
||||
}
|
||||
this.userId = decoded.aud;
|
||||
this.userId = claims.aud;
|
||||
this.user = await this.state.database.users.
|
||||
findById(this.userId, { devices: true }) || null;
|
||||
log.info({ userId: decoded.aud, name: decoded.name }, "authenticated websocket client");
|
||||
log.info({ userId: claims.aud, name: claims.name }, "authenticated websocket client");
|
||||
this.subscribeBrokerConnection();
|
||||
return {
|
||||
result: "success",
|
||||
|
Loading…
x
Reference in New Issue
Block a user