diff --git a/examples/oak-localstorage/loader.ts b/examples/oak-localstorage/loader.ts index 0d22d94..e52bc42 100644 --- a/examples/oak-localstorage/loader.ts +++ b/examples/oak-localstorage/loader.ts @@ -2,8 +2,8 @@ import { clientService, userService } from "./services/mod.ts"; localStorage.clear(); -userService.put({ username: "kyle", password: "qwerty" }); -userService.put({ username: "john", password: "doe" }); +userService.create({ username: "kyle", password: "qwerty" }); +userService.create({ username: "john", password: "doe" }); clientService.put({ id: "1000", diff --git a/examples/oak-localstorage/main.ts b/examples/oak-localstorage/main.ts index 4608724..6cd2383 100644 --- a/examples/oak-localstorage/main.ts +++ b/examples/oak-localstorage/main.ts @@ -179,10 +179,10 @@ router if (expiresIn) { session.accessTokenExpiresAt = new Date(now + (expiresIn * 1000)); } - session.user = undefined; - session.state = undefined; - session.codeVerifier = undefined; - session.redirectUri = undefined; + session.user = null; + session.state = null; + session.codeVerifier = null; + session.redirectUri = null; await sessionService.patch(session); response.redirect(redirectUri); } else { diff --git a/examples/oak-localstorage/models/client.ts b/examples/oak-localstorage/models/client.ts index fee9e37..2ec4d81 100644 --- a/examples/oak-localstorage/models/client.ts +++ b/examples/oak-localstorage/models/client.ts @@ -3,7 +3,7 @@ import { User } from "./user.ts"; export interface Client extends ClientInterface { /** Secret used for authenticating a client. */ - secret?: string; + secret?: string | null; /** A user that is controlled by the client. */ - user?: User; + user?: User | null; } diff --git a/examples/oak-localstorage/models/session.ts b/examples/oak-localstorage/models/session.ts index add13c2..2a9079e 100644 --- a/examples/oak-localstorage/models/session.ts +++ b/examples/oak-localstorage/models/session.ts @@ -3,11 +3,11 @@ import { User } from "./user.ts"; export interface Session { id: string; csrf: string; - user?: User; - state?: string; - redirectUri?: string; - codeVerifier?: string; - accessToken?: string; - refreshToken?: string; - accessTokenExpiresAt?: Date; + user?: User | null; + state?: string | null; + redirectUri?: string | null; + codeVerifier?: string | null; + accessToken?: string | null; + refreshToken?: string | null; + accessTokenExpiresAt?: Date | null; } diff --git a/examples/oak-localstorage/models/user.ts b/examples/oak-localstorage/models/user.ts index 2496a72..b9dfab1 100644 --- a/examples/oak-localstorage/models/user.ts +++ b/examples/oak-localstorage/models/user.ts @@ -1,5 +1,6 @@ export interface User { + id: string; username: string; - password?: string; - email?: string; + password?: string | null; + email?: string | null; } diff --git a/examples/oak-localstorage/services/authorization_code.ts b/examples/oak-localstorage/services/authorization_code.ts index ceda1b3..5d10282 100644 --- a/examples/oak-localstorage/services/authorization_code.ts +++ b/examples/oak-localstorage/services/authorization_code.ts @@ -12,7 +12,7 @@ interface AuthorizationCodeInternal { code: string; expiresAt: string; clientId: string; - username: string; + userId: string; scope?: string; redirectUri?: string; challenge?: string; @@ -47,14 +47,52 @@ export class AuthorizationCodeService code, expiresAt: expiresAt.toJSON(), clientId: client.id, - username: user.username, - scope: scope?.toJSON(), + userId: user.id, + }; + if (scope) next.scope = scope.toJSON(); + if (redirectUri) next.redirectUri = redirectUri; + if (challenge) next.challenge = challenge; + if (challengeMethod) next.challengeMethod = challengeMethod; + localStorage.setItem(`authorizationCode:${code}`, JSON.stringify(next)); + return Promise.resolve(); + } + + async patch( + authorizationCode: + & Partial> + & Pick, "code">, + ): Promise { + const { + code, + expiresAt, + client, + user, + scope, redirectUri, challenge, challengeMethod, - }; + } = authorizationCode; + const current = await this.getInternal(code); + if (!current) throw new Error("authorization code not found"); + const next: AuthorizationCodeInternal = { ...current, code }; + + if (expiresAt) next.expiresAt = expiresAt.toJSON(); + if (client) next.clientId = client.id; + if (user) next.userId = user.id; + + if (scope) next.scope = scope.toJSON(); + else if (scope === null) delete next.scope; + + if (redirectUri) next.redirectUri = redirectUri; + else if (redirectUri === null) delete next.redirectUri; + + if (challenge) next.challenge = challenge; + else if (challenge === null) delete next.challenge; + + if (challengeMethod) next.challengeMethod = challengeMethod; + else if (challengeMethod === null) delete next.challengeMethod; + localStorage.setItem(`authorizationCode:${code}`, JSON.stringify(next)); - return Promise.resolve(); } delete( @@ -69,44 +107,50 @@ export class AuthorizationCodeService return Promise.resolve(existed); } - async get( + private getInternal( code: string, - ): Promise | undefined> { + ): Promise { const internalText = localStorage.getItem(`authorizationCode:${code}`); - const internal: AuthorizationCodeInternal | undefined = internalText - ? JSON.parse(internalText) - : undefined; - let authorizationCode: AuthorizationCode | undefined = - undefined; - if (internal) { - const { + return Promise.resolve(internalText ? JSON.parse(internalText) : undefined); + } + + private async toExternal( + internal: AuthorizationCodeInternal, + ): Promise | undefined> { + const { + code, + expiresAt, + clientId, + userId, + scope, + redirectUri, + challenge, + challengeMethod, + } = internal; + const client = await this.clientService.get(clientId); + const user = client && await this.userService.get(userId); + if (client && user) { + const authorizationCode: AuthorizationCode = { code, - expiresAt, - clientId, - username, - scope, + expiresAt: new Date(expiresAt), + client, + user, redirectUri, challenge, challengeMethod, - } = internal; - const client = await this.clientService.get(clientId); - const user = client && await this.userService.get(username); - if (client && user) { - authorizationCode = { - code, - expiresAt: new Date(expiresAt), - client, - user, - redirectUri, - challenge, - challengeMethod, - }; - if (scope) authorizationCode.scope = Scope.from(scope); - } else { - await this.delete(code); - } + }; + if (scope) authorizationCode.scope = Scope.from(scope); + return authorizationCode; + } else { + await this.delete(code); } - return authorizationCode; + } + + async get( + code: string, + ): Promise | undefined> { + const internal = await this.getInternal(code); + return internal ? await this.toExternal(internal) : undefined; } async save( diff --git a/examples/oak-localstorage/services/client.ts b/examples/oak-localstorage/services/client.ts index dcfeac9..c6d4463 100644 --- a/examples/oak-localstorage/services/client.ts +++ b/examples/oak-localstorage/services/client.ts @@ -10,7 +10,7 @@ interface ClientInternal { redirectUris?: string[]; accessTokenLifetime?: number; refreshTokenLifetime?: number; - username?: string; + userId?: string; } export class ClientService extends AbstractClientService { @@ -31,16 +31,13 @@ export class ClientService extends AbstractClientService { refreshTokenLifetime, user, } = client; - const { username } = user ?? {}; - const next: ClientInternal = { - id, - secret, - grants, - redirectUris, - accessTokenLifetime, - refreshTokenLifetime, - username, - }; + const next: ClientInternal = { id }; + if (secret) next.secret = secret; + if (grants) next.grants = grants; + if (redirectUris) next.redirectUris = redirectUris; + if (accessTokenLifetime) next.accessTokenLifetime = accessTokenLifetime; + if (refreshTokenLifetime) next.refreshTokenLifetime = refreshTokenLifetime; + if (user) next.userId = user.id; localStorage.setItem(`client:${id}`, JSON.stringify(next)); return Promise.resolve(); } @@ -55,22 +52,35 @@ export class ClientService extends AbstractClientService { refreshTokenLifetime, user, } = client; - const { username } = user ?? {}; const current = await this.getInternal(id); if (!current) throw new Error("client not found"); - const next: ClientInternal = { ...current }; - if ("grants" in client) next.grants = grants; - if ("secret" in client) next.secret = secret; - if ("redirectUris" in client) next.redirectUris = redirectUris; - if ("accessTokenLifetime" in client) { + const next: ClientInternal = { ...current, id }; + + if (grants) next.grants = grants; + else if (grants === null) delete next.grants; + + if (secret) next.secret = secret; + else if (secret === null) delete next.secret; + + if (redirectUris) next.redirectUris = redirectUris; + else if (redirectUris === null) delete next.redirectUris; + + if (accessTokenLifetime) { next.accessTokenLifetime = accessTokenLifetime; + } else if (accessTokenLifetime === null) { + delete next.accessTokenLifetime; } - if ("refreshTokenLifetime" in client) { + + if (refreshTokenLifetime) { next.refreshTokenLifetime = refreshTokenLifetime; + } else if (refreshTokenLifetime === null) { + delete next.refreshTokenLifetime; } - if ("user" in client) next.username = username; + + if (user) next.userId = user.id; + else if (user === null) delete next.userId; + localStorage.setItem(`client:${id}`, JSON.stringify(next)); - return Promise.resolve(); } delete(client: Client | string): Promise { @@ -107,7 +117,7 @@ export class ClientService extends AbstractClientService { async get(clientId: string): Promise { const internal = await this.getInternal(clientId); - return internal ? this.toExternal(internal) : undefined; + return internal ? await this.toExternal(internal) : undefined; } async getAuthenticated( @@ -125,8 +135,8 @@ export class ClientService extends AbstractClientService { async getUser(client: Client | string): Promise { const clientId = typeof client === "string" ? client : client.id; const internal = await this.getInternal(clientId); - return internal?.username - ? await this.userService.get(internal.username) + return internal?.userId + ? await this.userService.get(internal.userId) : undefined; } } diff --git a/examples/oak-localstorage/services/session.ts b/examples/oak-localstorage/services/session.ts index c0944d8..e8f8c80 100644 --- a/examples/oak-localstorage/services/session.ts +++ b/examples/oak-localstorage/services/session.ts @@ -4,7 +4,7 @@ import { UserService } from "./user.ts"; interface SessionInternal { id: string; csrf: string; - username?: string; + userId?: string; authorizedScope?: string; state?: string; redirectUri?: string; @@ -37,19 +37,16 @@ export class SessionService { accessTokenExpiresAt, csrf, } = session; - const next: SessionInternal = { - id, - state, - redirectUri, - codeVerifier, - accessToken, - refreshToken, - csrf, - username: user?.username, - }; + const next: SessionInternal = { id, csrf }; + if (state) next.state = state; + if (redirectUri) next.redirectUri = redirectUri; + if (codeVerifier) next.codeVerifier = codeVerifier; + if (accessToken) next.accessToken = accessToken; if (accessTokenExpiresAt) { next.accessTokenExpiresAt = accessTokenExpiresAt?.valueOf(); } + if (refreshToken) next.refreshToken = refreshToken; + if (user) next.userId = user.id; localStorage.setItem(`session:${id}`, JSON.stringify(next)); return Promise.resolve(); } @@ -66,20 +63,34 @@ export class SessionService { accessTokenExpiresAt, csrf, } = session; - const { username } = user ?? {}; const current = await this.getInternal(id); if (!current) throw new Error("session not found"); - const next: SessionInternal = { ...current }; - if ("user" in session) next.username = username; - if ("state" in session) next.state = state; - if ("redirectUri" in session) next.redirectUri = redirectUri; - if ("codeVerifier" in session) next.codeVerifier = codeVerifier; - if ("accessToken" in session) next.accessToken = accessToken; - if ("refreshToken" in session) next.refreshToken = refreshToken; - if ("accessTokenExpiresAt" in session) { + const next: SessionInternal = { ...current, id }; + + if (user) next.userId = user.id; + else if (user === null) delete next.userId; + + if (state) next.state = state; + else if (state === null) delete next.state; + + if (redirectUri) next.redirectUri = redirectUri; + else if (redirectUri === null) delete next.redirectUri; + + if (codeVerifier) next.codeVerifier = codeVerifier; + else if (codeVerifier === null) delete next.codeVerifier; + + if (accessToken) next.accessToken = accessToken; + else if (accessToken === null) delete next.accessToken; + + if (accessTokenExpiresAt) { next.accessTokenExpiresAt = accessTokenExpiresAt?.valueOf(); - } - if ("csrf" in session) next.csrf = csrf ?? crypto.randomUUID(); + } else if (accessTokenExpiresAt === null) delete next.accessTokenExpiresAt; + + if (refreshToken) next.refreshToken = refreshToken; + else if (refreshToken === null) delete next.refreshToken; + + if (csrf) next.csrf = csrf; + localStorage.setItem(`session:${id}`, JSON.stringify(next)); await Promise.resolve(); } @@ -107,7 +118,7 @@ export class SessionService { refreshToken, accessTokenExpiresAt, csrf, - username, + userId, } = internal; const external: Session = { id, @@ -121,7 +132,7 @@ export class SessionService { if (accessTokenExpiresAt) { external.accessTokenExpiresAt = new Date(accessTokenExpiresAt); } - if (username) external.user = await this.userService.get(username); + if (userId) external.user = await this.userService.get(userId); return external; } diff --git a/examples/oak-localstorage/services/token.ts b/examples/oak-localstorage/services/token.ts index 675876c..5c66d2d 100644 --- a/examples/oak-localstorage/services/token.ts +++ b/examples/oak-localstorage/services/token.ts @@ -15,7 +15,7 @@ interface TokenInternal { refreshToken?: string; refreshTokenExpiresAt?: string; clientId: string; - username: string; + userId: string; scope?: string; code?: string; } @@ -31,6 +31,20 @@ export class TokenService this.userService = userService; } + private getTokenIndex( + token: + & Partial> + & Pick, "accessToken">, + ): string | null { + const accessTokenKey = `accessToken:${token.accessToken}`; + const refreshTokenKey = token.refreshToken + ? `refreshToken:${token.refreshToken}` + : undefined; + return localStorage.getItem(accessTokenKey) ?? + (refreshTokenKey && localStorage.getItem(refreshTokenKey)) ?? + null; + } + put(token: Token): Promise { const { accessToken, @@ -42,12 +56,7 @@ export class TokenService scope, code, } = token; - const accessTokenKey = `accessToken:${token.accessToken}`; - const refreshTokenKey = token.refreshToken - ? `refreshToken:${token.refreshToken}` - : undefined; - let tokenIndex = localStorage.getItem(accessTokenKey) || - (refreshTokenKey && localStorage.getItem(refreshTokenKey)); + let tokenIndex = this.getTokenIndex(token); if (!tokenIndex) { tokenIndex = localStorage.getItem("nextTokenIndex") ?? "0"; localStorage.setItem("nextTokenIndex", `${parseInt(tokenIndex) + 1}`); @@ -59,22 +68,77 @@ export class TokenService localStorage.setItem(`tokenCode:${token.code}`, tokenIndex); } } + const next: TokenInternal = { + accessToken, + clientId: client.id, + userId: user.id, + }; + + if (accessTokenExpiresAt) { + next.accessTokenExpiresAt = accessTokenExpiresAt.toJSON(); + } + if (refreshToken) next.refreshToken = refreshToken; + if (refreshTokenExpiresAt) { + next.refreshTokenExpiresAt = refreshTokenExpiresAt.toJSON(); + } + if (scope) next.scope = scope.toJSON(); + if (code) next.code = code; + localStorage.setItem( `token:${tokenIndex}`, - JSON.stringify({ - accessToken, - accessTokenExpiresAt: accessTokenExpiresAt?.toJSON(), - refreshToken, - refreshTokenExpiresAt: refreshTokenExpiresAt?.toJSON(), - clientId: client.id, - username: user.username, - scope: scope?.toJSON(), - code, - } as TokenInternal), + JSON.stringify(next), ); return Promise.resolve(); } + async patch( + token: + & Partial> + & Pick, "accessToken">, + ): Promise { + const { + accessToken, + accessTokenExpiresAt, + refreshToken, + refreshTokenExpiresAt, + client, + user, + scope, + code, + } = token; + const tokenIndex = this.getTokenIndex(token); + const current = tokenIndex !== null && + await this.getTokenInternalByIndex(tokenIndex); + if (!current) throw new Error("token not found"); + const next: TokenInternal = { ...current, accessToken }; + + if (client) next.clientId = client.id; + if (user) next.userId = user.id; + + if (accessTokenExpiresAt) { + next.accessTokenExpiresAt = accessTokenExpiresAt.toJSON(); + } else if (accessTokenExpiresAt === null) { + delete next.accessTokenExpiresAt; + } + + if (refreshToken) next.refreshToken = refreshToken; + else if (refreshToken === null) delete next.refreshToken; + + if (refreshTokenExpiresAt) { + next.refreshTokenExpiresAt = refreshTokenExpiresAt.toJSON(); + } else if (refreshTokenExpiresAt === null) { + delete next.refreshTokenExpiresAt; + } + + if (scope) next.scope = scope.toJSON(); + else if (scope === null) delete next.scope; + + if (code) next.code = code; + else if (code === null) delete next.code; + + localStorage.setItem(`token:${tokenIndex}`, JSON.stringify(next)); + } + deleteAccessToken(accessToken: string): Promise { const accessTokenKey = `accessToken:${accessToken}`; const tokenIndex = localStorage.getItem(accessTokenKey); @@ -133,11 +197,15 @@ export class TokenService return await this.deleteToken(token); } - private async getTokenByIndex(tokenIndex: string) { + private getTokenInternalByIndex(tokenIndex: string): Promise { const internalText = localStorage.getItem(`token:${tokenIndex}`); - const internal: TokenInternal | undefined = internalText - ? JSON.parse(internalText) - : undefined; + return Promise.resolve(internalText ? JSON.parse(internalText) : undefined); + } + + private async getTokenByIndex( + tokenIndex: string, + ): Promise | undefined> { + const internal = await this.getTokenInternalByIndex(tokenIndex); let token: Token | undefined = undefined; if (internal) { const { @@ -146,12 +214,12 @@ export class TokenService refreshToken, refreshTokenExpiresAt, clientId, - username, + userId, scope, code, } = internal; const client = await this.clientService.get(clientId); - const user = client && await this.userService.get(username); + const user = client && await this.userService.get(userId); if (client && user) { token = { accessToken, diff --git a/examples/oak-localstorage/services/user.ts b/examples/oak-localstorage/services/user.ts index d898ab3..b0f4777 100644 --- a/examples/oak-localstorage/services/user.ts +++ b/examples/oak-localstorage/services/user.ts @@ -14,6 +14,7 @@ function hashPassword(password: string, salt: string): string { } interface UserInternal { + id: string; username: string; email?: string; hash?: string; @@ -25,55 +26,69 @@ export class UserService extends AbstractUserService { super(); } + create(user: Omit): Promise { + return this.put({ ...user, id: crypto.randomUUID() }) + } + put(user: User): Promise { - const { username, password, email } = user; - const next: UserInternal = { username, email }; + const { id, username, password, email } = user; + const next: UserInternal = { id, username }; + + if (email) next.email = email; if (password) { next.salt = generateSalt(); next.hash = hashPassword(password, next.salt); } - localStorage.setItem(`user:${username}`, JSON.stringify(next)); + + localStorage.setItem(`username:${username}`, id); + localStorage.setItem(`user:${id}`, JSON.stringify(next)); return Promise.resolve(); } - async patch(user: Partial & Pick): Promise { - const { username, email, password } = user; - const current = await this.getInternal(username); + async patch(user: Partial & Pick): Promise { + const { id, username, email, password } = user; + const current = await this.getInternal(id); if (!current) throw new Error("user not found"); - const next: UserInternal = { ...current, username }; - if ("email" in user) next.email = email; - if ("password" in user) { - if (password) { - next.salt = generateSalt(); - next.hash = hashPassword(password, next.salt); - } else { - delete next.salt; - delete next.hash; - } + const next: UserInternal = { ...current, id }; + + if (username) next.username = username; + + if (email) next.email = email; + else if (email === null) delete next.email; + + if (password) { + next.salt = generateSalt(); + next.hash = hashPassword(password, next.salt); + } else if (password === null) { + delete next.salt; + delete next.hash; } - localStorage.setItem(`user:${username}`, JSON.stringify(next)); + + localStorage.setItem(`user:${id}`, JSON.stringify(next)); } - delete(user: User | string): Promise { - const username = typeof user === "string" ? user : user.username; - const userKey = `user:${username}`; - const existed = !!localStorage.getItem(userKey); - localStorage.removeItem(userKey); - return Promise.resolve(existed); + async delete(user: User | string): Promise { + const id = typeof user === "string" ? user : user.id; + const internal = await this.getInternal(id); + if (internal) { + localStorage.removeItem(`username:${internal.username}`); + localStorage.removeItem(`user:${id}`); + } + return Promise.resolve(!!internal); } - private getInternal(username: string): Promise { - const internalText = localStorage.getItem(`user:${username}`); + private getInternal(id: string): Promise { + const internalText = localStorage.getItem(`user:${id}`); return Promise.resolve(internalText ? JSON.parse(internalText) : undefined); } private toExternal(internal: UserInternal): Promise { - const { username, email } = internal; - return Promise.resolve({ username, email }); + const { id, username, email } = internal; + return Promise.resolve({ id, username, email }); } - async get(username: string): Promise { - const internal = await this.getInternal(username); + async get(id: string): Promise { + const internal = await this.getInternal(id); return internal ? await this.toExternal(internal) : undefined; } @@ -81,7 +96,8 @@ export class UserService extends AbstractUserService { username: string, password: string, ): Promise { - const internal = await this.getInternal(username); + const id = localStorage.getItem(`username:${username}`); + const internal = id && await this.getInternal(id); let user: User | undefined = undefined; if (internal) { const { hash, salt } = internal; diff --git a/grants/client_credentials.ts b/grants/client_credentials.ts index 0663a9b..4c6c405 100644 --- a/grants/client_credentials.ts +++ b/grants/client_credentials.ts @@ -62,7 +62,7 @@ export class ClientCredentialsGrant< const body: URLSearchParams = await request.body!; const scopeText: string | null = body.get("scope"); - let scope: Scope | undefined = this.parseScope(scopeText); + let scope: Scope | null | undefined = this.parseScope(scopeText); const { tokenService, clientService } = this.services; const user: User | void = await clientService.getUser(client); diff --git a/grants/grant.ts b/grants/grant.ts index 1031830..93346ef 100644 --- a/grants/grant.ts +++ b/grants/grant.ts @@ -43,8 +43,8 @@ export interface GrantInterface< acceptedScope( client: Client, user: User, - scope?: Scope, - ): Promise; + scope?: Scope | null, + ): Promise; getClientCredentials( request: OAuth2Request, ): Promise; @@ -54,7 +54,7 @@ export interface GrantInterface< generateToken( client: Client, user: User, - scope?: Scope, + scope?: Scope | null, ): Promise>; token( request: OAuth2Request, @@ -92,7 +92,7 @@ export abstract class AbstractGrant< client: Client, user: User, scope?: Scope, - ): Promise { + ): Promise { const { tokenService } = this.services; const acceptedScope = await tokenService.acceptedScope(client, user, scope); if (acceptedScope === false) { @@ -143,7 +143,7 @@ export abstract class AbstractGrant< async generateToken( client: Client, user: User, - scope?: Scope, + scope?: Scope | null, ): Promise> { const { tokenService } = this.services; const token: Token = { diff --git a/grants/password.ts b/grants/password.ts index 9580303..82f08d9 100644 --- a/grants/password.ts +++ b/grants/password.ts @@ -61,7 +61,7 @@ export class PasswordGrant< const body: URLSearchParams = await request.body!; const scopeText: string | null = body.get("scope"); - let scope: Scope | undefined = this.parseScope(scopeText); + let scope: Scope | null | undefined = this.parseScope(scopeText); const username: string | null = body.get("username"); if (!username) throw new InvalidRequest("username parameter required"); diff --git a/models/authorization_code.ts b/models/authorization_code.ts index 0b54dd7..2e4e94d 100644 --- a/models/authorization_code.ts +++ b/models/authorization_code.ts @@ -15,11 +15,11 @@ export interface AuthorizationCode< /** The user associated with the authorization code. */ user: User; /** The scope granted to the authorization code. */ - scope?: Scope; + scope?: Scope | null; /** Redirect URI for the authorization code. */ - redirectUri?: string; + redirectUri?: string | null; /** The code challenge used for PKCE. */ - challenge?: string; + challenge?: string | null; /** The code challenge method used for PKCE. */ - challengeMethod?: string; + challengeMethod?: string | null; } diff --git a/models/client.ts b/models/client.ts index 90c80fc..e3e4173 100644 --- a/models/client.ts +++ b/models/client.ts @@ -2,13 +2,13 @@ export interface ClientInterface { /** A unique identifier. */ id: string; /** Grant types allowed for the client. */ - grants?: string[]; + grants?: string[] | null; /** Redirect URIs allowed for the client. Required for the `authorization_code` grant type. */ - redirectUris?: string[]; + redirectUris?: string[] | null; /** Client specific lifetime of access tokens in seconds. */ - accessTokenLifetime?: number; + accessTokenLifetime?: number | null; /** Client specific lifetime of refresh tokens in seconds. */ - refreshTokenLifetime?: number; + refreshTokenLifetime?: number | null; } // deno-lint-ignore no-empty-interface diff --git a/models/token.ts b/models/token.ts index f6f9993..5fd2cd3 100644 --- a/models/token.ts +++ b/models/token.ts @@ -9,15 +9,15 @@ export interface AccessToken< /** The access token. */ accessToken: string; /** The expiration time for the access token. */ - accessTokenExpiresAt?: Date; + accessTokenExpiresAt?: Date | null; /** The client associated with the token. */ client: Client; /** The user associated with the token. */ user: User; /** The scope granted to the token. */ - scope?: Scope; + scope?: Scope | null; /** The authorization code used to issue the token. */ - code?: string; + code?: string | null; } export interface Token< @@ -26,9 +26,9 @@ export interface Token< Scope extends ScopeInterface, > extends AccessToken { /** The refresh token. */ - refreshToken?: string; + refreshToken?: string | null; /** The expiration time for the refresh token. */ - refreshTokenExpiresAt?: Date; + refreshTokenExpiresAt?: Date | null; } export interface RefreshToken< diff --git a/server.ts b/server.ts index 73b49d7..3d193df 100644 --- a/server.ts +++ b/server.ts @@ -418,7 +418,7 @@ export class OAuth2Server< throw new InvalidRequest("response_type not supported"); } - let scope: Scope | undefined = grant.parseScope(scopeText); + let scope: Scope | null | undefined = grant.parseScope(scopeText); request.requestedScope = scope; if (challengeMethod && !challenge) { diff --git a/services/authorization_code.ts b/services/authorization_code.ts index cc2143d..05fe58c 100644 --- a/services/authorization_code.ts +++ b/services/authorization_code.ts @@ -13,13 +13,13 @@ export interface AuthorizationCodeServiceInterface< generateCode( client: Client, user: User, - scope?: Scope, + scope?: Scope | null, ): Promise; /** Gets the date that a new authorization code would expire at. */ expiresAt( client: Client, user: User, - scope?: Scope, + scope?: Scope | null, ): Promise; /** Retrieves an existing authorization code. */ get(code: string): Promise | void>; @@ -44,7 +44,7 @@ export abstract class AbstractAuthorizationCodeService< generateCode( _client: Client, _user: User, - _scope?: Scope, + _scope?: Scope | null, ): Promise { return Promise.resolve(crypto.randomUUID()); } @@ -52,7 +52,7 @@ export abstract class AbstractAuthorizationCodeService< expiresAt( _client: Client, _user: User, - _scope?: Scope, + _scope?: Scope | null, ): Promise { return Promise.resolve( new Date(Date.now() + this.lifetime * 1000), diff --git a/services/token.ts b/services/token.ts index e8427fd..bed03f2 100644 --- a/services/token.ts +++ b/services/token.ts @@ -16,31 +16,31 @@ export interface TokenServiceInterface< acceptedScope( client: Client, user: User, - scope?: Scope, - ): Promise; + scope?: Scope | null, + ): Promise; /** Generates an access token. */ generateAccessToken( client: Client, user: User, - scope?: Scope, + scope?: Scope | null, ): Promise; /** Generates a refresh token. */ generateRefreshToken( client: Client, user: User, - scope?: Scope, + scope?: Scope | null, ): Promise; /** Gets the date that a new access token would expire at. */ accessTokenExpiresAt( client: Client, user: User, - scope?: Scope, + scope?: Scope | null, ): Promise; /** Gets the date that a new refresh token would expire at. */ refreshTokenExpiresAt( client: Client, user: User, - scope?: Scope, + scope?: Scope | null, ): Promise; /** Retrieves an existing token by access token. */ getToken( @@ -72,8 +72,8 @@ export abstract class AbstractAccessTokenService< acceptedScope( _client: Client, _user: User, - scope?: Scope, - ): Promise { + scope?: Scope | null, + ): Promise { return Promise.resolve(scope); } @@ -81,7 +81,7 @@ export abstract class AbstractAccessTokenService< generateAccessToken( _client: Client, _user: User, - _scope?: Scope, + _scope?: Scope | null, ): Promise { return Promise.resolve(crypto.randomUUID()); } @@ -90,7 +90,7 @@ export abstract class AbstractAccessTokenService< generateRefreshToken( _client: Client, _user: User, - _scope?: Scope, + _scope?: Scope | null, ): Promise { return Promise.reject( new ServerError("generateRefreshToken not implemented"), @@ -101,7 +101,7 @@ export abstract class AbstractAccessTokenService< accessTokenExpiresAt( client: Client, _user: User, - _scope?: Scope, + _scope?: Scope | null, ): Promise { const lifetime: number = client.accessTokenLifetime ?? this.accessTokenLifetime; @@ -114,7 +114,7 @@ export abstract class AbstractAccessTokenService< refreshTokenExpiresAt( _client: Client, _user: User, - _scope?: Scope, + _scope?: Scope | null, ): Promise { return Promise.reject( new ServerError("refreshTokenExpiresAt not implemented"), @@ -158,7 +158,7 @@ export abstract class AbstractRefreshTokenService< generateRefreshToken( _client: Client, _user: User, - _scope?: Scope, + _scope?: Scope | null, ): Promise { return Promise.resolve(crypto.randomUUID()); } @@ -167,7 +167,7 @@ export abstract class AbstractRefreshTokenService< refreshTokenExpiresAt( client: Client, _user: User, - _scope?: Scope, + _scope?: Scope | null, ): Promise { const lifetime: number = client.refreshTokenLifetime ?? this.refreshTokenLifetime;