@stacksjs/auth 0.70.368 → 0.70.370
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authentication.js +1 -1
- package/package.json +3 -3
package/dist/authentication.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{config}from"@stacksjs/config";import{db,parseSqlDateTime}from"@stacksjs/database";import{HttpError}from"@stacksjs/error-handling";import{formatDate,User}from"@stacksjs/orm";import{getCurrentRequest,request}from"@stacksjs/router";import{requestToken}from"./request-token";import{Buffer}from"node:buffer";import{createHash,randomBytes,timingSafeEqual}from"node:crypto";import{decrypt,encrypt,verifyHash}from"@stacksjs/security";import{log}from"@stacksjs/logging";import{DUMMY_BCRYPT_HASH}from"./internal-constants";import{RateLimiter}from"./rate-limiter";const REQUEST_AUTH_STATE_KEY=Symbol.for("stacks.requestAuthState");function authStateOrNull(){const req=getCurrentRequest();if(!req)return null;let state=req[REQUEST_AUTH_STATE_KEY];if(!state){state={};req[REQUEST_AUTH_STATE_KEY]=state}return state}function hashToken(token){return createHash("sha256").update(token).digest("hex")}import{createToken as createRawToken,getPasswordChangedAt,isIssuedBeforePasswordChange,parseScopes}from"./tokens";export class Auth{static getBearerToken(){return requestToken(request)}static parseToken(token){const firstColonIndex=token.indexOf(":");if(firstColonIndex===-1)return null;const plainToken=token.substring(0,firstColonIndex),encryptedId=token.substring(firstColonIndex+1);if(!plainToken||!encryptedId)return null;return{plainToken,encryptedId}}static async getClientSecret(){const state=authStateOrNull();if(state?.clientSecret)return state.clientSecret;const client=await this.getPersonalAccessClient();if(state)state.clientSecret=client.secret;return client.secret}static async encryptTokenId(id){return await encrypt(String(id))}static async decryptTokenId(encryptedId){try{return await decrypt(encryptedId)}catch{try{const clientSecret=await this.getClientSecret();return await decrypt(encryptedId,clientSecret)}catch{return null}}}static async getPersonalAccessClient(){try{const client=await db.selectFrom("oauth_clients").where("personal_access_client","=",!0).where("revoked","=",!1).selectAll().executeTakeFirst();if(!client)throw new HttpError(500,"No personal access client found. Please run `./buddy auth:setup` first.");return client}catch(error){if(error instanceof Error&&error.message.includes("does not exist"))throw new HttpError(500,"OAuth tables not found. Please run `./buddy auth:setup` first.");throw error}}static async validateClient(clientId,clientSecret){const client=await db.selectFrom("oauth_clients").where("id","=",clientId).where("revoked","=",!1).selectAll().executeTakeFirst(),provided=Buffer.from(clientSecret);if(!client?.secret){const dummy=Buffer.alloc(Math.max(provided.length,1)),padded=provided.length>0?provided:Buffer.alloc(1);timingSafeEqual(dummy,padded);return!1}const stored=String(client.secret);if(stored.startsWith("$2"))return await verifyHash(clientSecret,stored);const storedBuf=Buffer.from(stored);if(storedBuf.length!==provided.length){timingSafeEqual(storedBuf,storedBuf);return!1}return timingSafeEqual(storedBuf,provided)}static async getTokenFromId(tokenId){const result=await db.selectFrom("oauth_access_tokens").where("id","=",tokenId).selectAll().executeTakeFirst();if(!result)return null;const token=result;return{id:token.id,userId:token.user_id,clientId:token.oauth_client_id,name:token.name||"auth-token",scopes:parseScopes(token.scopes),abilities:parseScopes(token.scopes),expiresAt:parseSqlDateTime(token.expires_at),createdAt:token.created_at?new Date(String(token.created_at)):new Date,updatedAt:token.updated_at?new Date(String(token.updated_at)):new Date,revoked:!!token.revoked}}static async attempt(credentials){const username=config.auth.username||"email",password=config.auth.password||"password",email=credentials[username];if(!email)return!1;const isRateLimited=await RateLimiter.isRateLimited(email),user=await User.where("email","=",email).first(),authPass=credentials[password]||"",hashToVerify=user?.password||DUMMY_BCRYPT_HASH,hashCheck=await verifyHash(authPass,hashToVerify);if(isRateLimited)return!1;if(hashCheck&&user){await RateLimiter.resetAttempts(email);const state=authStateOrNull();if(state)state.authUser=user;return!0}await RateLimiter.recordFailedAttempt(email);return!1}static async validate(credentials){const username=config.auth.username||"email",password=config.auth.password||"password",email=credentials[username];if(!email)return!1;const user=await User.where("email","=",email).first(),authPass=credentials[password]||"",hashToVerify=user?.password||DUMMY_BCRYPT_HASH;return await verifyHash(authPass,hashToVerify)&&!!user}static async login(credentials,options){if(!await this.attempt(credentials))return null;const username=config.auth.username||"email",authedUser=authStateOrNull()?.authUser??await User.where(username,"=",credentials[username]).first();if(!authedUser)return null;const{plainTextToken,refreshToken,expiresIn}=await this.createTokenForUser(authedUser,options);return{user:authedUser,token:plainTextToken,refreshToken,expiresIn}}static async loginUsingId(userId,options){const user=await User.find(userId);if(!user)return null;const state=authStateOrNull();if(state)state.authUser=user;const{plainTextToken,refreshToken,expiresIn}=await this.createTokenForUser(user,options);return{user,token:plainTextToken,refreshToken,expiresIn}}static async logout(){const bearerToken=this.getBearerToken();if(bearerToken)await this.revokeToken(bearerToken);const state=authStateOrNull();if(state){state.authUser=void 0;state.currentToken=void 0}}static async user(){const state=authStateOrNull();if(state?.authUser)return state.authUser;const bearerToken=this.getBearerToken();if(!bearerToken)return;const user=await this.getUserFromToken(bearerToken);if(user&&state)state.authUser=user;return user}static async check(){return await this.user()!==void 0}static async guest(){return!await this.check()}static async id(){return(await this.user())?.id}static setUser(user){const state=authStateOrNull();if(state)state.authUser=user}static async createTokenForUser(user,options){const name=options?.name??config.auth.defaultTokenName??"auth-token",abilities=options?.abilities??options?.scopes??config.auth.defaultAbilities??["*"],accessTtlMs=options?.expiresInMinutes!==void 0?options.expiresInMinutes*60*1000:config.auth.tokenExpiry??3600000,expiresAt=options?.expiresAt??new Date(Date.now()+accessTtlMs),expiresInMinutes=Math.max(1,Math.floor((expiresAt.getTime()-Date.now())/60000)),refreshExpiresInDays=options?.refreshExpiresInDays??Math.max(1,Math.round((config.auth.refreshTokenExpiry??2592000000)/86400000));log.debug(`[auth] Creating token for user#${user.id}: ${name}`);const result=await createRawToken(user.id,name,abilities,{expiresInMinutes,withRefreshToken:options?.withRefreshToken!==!1,refreshExpiresInDays,userAgent:options?.userAgent??null,ipAddress:options?.ipAddress??null}),plainTextToken=result.plainTextToken;return{accessToken:{id:result.accessToken.id,userId:result.accessToken.userId,clientId:result.accessToken.clientId,name:result.accessToken.name,scopes:result.accessToken.scopes,abilities,expiresAt:result.accessToken.expiresAt??expiresAt,createdAt:result.accessToken.createdAt,updatedAt:result.accessToken.updatedAt,revoked:result.accessToken.revoked,plainTextToken},plainTextToken,refreshToken:result.refreshToken,expiresIn:result.expiresIn}}static async createToken(user,name=config.auth.defaultTokenName||"auth-token",abilities=config.auth.defaultAbilities||["*"]){const{plainTextToken}=await this.createTokenForUser(user,{name,abilities});return plainTextToken}static async requestToken(credentials,clientId,clientSecret){if(!await this.validateClient(clientId,clientSecret))throw new HttpError(401,"Invalid client credentials");const isValid=await this.attempt(credentials),authedUser=authStateOrNull()?.authUser;if(!isValid||!authedUser)return null;return{token:await this.createToken(authedUser,"user-auth-token")}}static async validateToken(token){const hashedPlainToken=hashToken(token),accessToken=await db.selectFrom("oauth_access_tokens").where("token","=",hashedPlainToken).selectAll().executeTakeFirst();if(!accessToken)return!1;log.debug(`[auth] Token validated for token#${accessToken.id}`);if(accessToken.expires_at&&(parseSqlDateTime(accessToken.expires_at)??new Date(0))<new Date){await db.deleteFrom("oauth_access_tokens").where("id","=",accessToken.id).execute();return!1}if(accessToken.revoked)return!1;if(isIssuedBeforePasswordChange(accessToken.created_at,await getPasswordChangedAt(accessToken.user_id)))return!1;await db.updateTable("oauth_access_tokens").set({updated_at:formatDate(new Date)}).where("id","=",accessToken.id).execute();return!0}static async getUserFromToken(token){const hashedPlainToken=hashToken(token),accessToken=await db.selectFrom("oauth_access_tokens").where("token","=",hashedPlainToken).selectAll().executeTakeFirst();if(!accessToken)return;if(accessToken.expires_at&&(parseSqlDateTime(accessToken.expires_at)??new Date(0))<new Date){await db.deleteFrom("oauth_access_tokens").where("id","=",accessToken.id).execute();return}if(accessToken.revoked)return;const idleMs=config.auth?.idleTimeout??0;if(idleMs>0){const lastSeen=parseSqlDateTime(accessToken.updated_at??accessToken.created_at);if(lastSeen&&Date.now()-lastSeen.getTime()>idleMs){await db.updateTable("oauth_access_tokens").set({revoked:!0}).where("id","=",accessToken.id).execute();return}}const stateForToken=authStateOrNull();if(stateForToken)stateForToken.currentToken=await this.getTokenFromId(accessToken.id)??void 0;await db.updateTable("oauth_access_tokens").set({updated_at:formatDate(new Date)}).where("id","=",accessToken.id).execute();if(!accessToken?.user_id)return;const user=await User.find(accessToken.user_id);if(isIssuedBeforePasswordChange(accessToken.created_at,await getPasswordChangedAt(accessToken.user_id)))return;return user}static async currentAccessToken(){const state=authStateOrNull();if(state?.currentToken)return state.currentToken;const bearerToken=this.getBearerToken();if(!bearerToken)return;const accessToken=await db.selectFrom("oauth_access_tokens").where("token","=",hashToken(bearerToken)).select(["id"]).executeTakeFirst();if(!accessToken)return;const token=await this.getTokenFromId(Number(accessToken.id));if(token&&state)state.currentToken=token;return token??void 0}static async tokenCan(ability){const token=await this.currentAccessToken();if(!token)return!1;if(token.abilities.includes("*"))return!0;return token.abilities.includes(ability)}static async tokenCant(ability){return!await this.tokenCan(ability)}static async tokenAbilities(){return(await this.currentAccessToken())?.abilities??[]}static async tokenCanAll(abilities){const token=await this.currentAccessToken();if(!token)return!1;if(token.abilities.includes("*"))return!0;return abilities.every((a)=>token.abilities.includes(a))}static async tokenCanAny(abilities){const token=await this.currentAccessToken();if(!token)return!1;if(token.abilities.includes("*"))return!0;return abilities.some((a)=>token.abilities.includes(a))}static async tokens(userId){const uid=userId??await this.id();if(!uid)return[];return(await db.selectFrom("oauth_access_tokens").where("user_id","=",uid).where("revoked","=",!1).selectAll().execute()).map((token)=>({id:Number(token.id),userId:Number(token.user_id),clientId:Number(token.oauth_client_id),name:String(token.name||"auth-token"),scopes:parseScopes(String(token.scopes??"")),abilities:parseScopes(String(token.scopes??"")),expiresAt:parseSqlDateTime(token.expires_at),createdAt:token.created_at?new Date(String(token.created_at)):new Date,updatedAt:token.updated_at?new Date(String(token.updated_at)):new Date,revoked:!!token.revoked}))}static async revokeToken(token){const accessToken=await db.selectFrom("oauth_access_tokens").where("token","=",hashToken(token)).select(["id"]).executeTakeFirst();if(accessToken)await this.revokeRefreshTokensFor(Number(accessToken.id));await db.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:formatDate(new Date)}).where("token","=",hashToken(token)).execute()}static async revokeTokenById(tokenId){await this.revokeRefreshTokensFor(tokenId);await db.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:formatDate(new Date)}).where("id","=",tokenId).execute()}static async revokeRefreshTokensFor(accessTokenId){await db.updateTable("oauth_refresh_tokens").set({revoked:!0}).where("access_token_id","=",accessTokenId).execute()}static async revokeAllTokens(userId){const uid=userId??await this.id();if(!uid)return;const accessTokens=await db.selectFrom("oauth_access_tokens").where("user_id","=",uid).select(["id"]).execute();for(const accessToken of accessTokens)await this.revokeRefreshTokensFor(Number(accessToken.id));await db.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:formatDate(new Date)}).where("user_id","=",uid).execute()}static async revokeOtherTokens(userId){const uid=userId??await this.id();if(!uid)return;const currentToken=await this.currentAccessToken();if(!currentToken)return;await db.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:formatDate(new Date)}).where("user_id","=",uid).where("id","!=",currentToken.id).execute()}static async pruneExpiredTokens(){const result=await db.deleteFrom("oauth_access_tokens").where("expires_at","<",formatDate(new Date)).executeTakeFirst();return Number(result?.numDeletedRows)||0}static async pruneRevokedTokens(){const result=await db.deleteFrom("oauth_access_tokens").where("revoked","=",!0).executeTakeFirst();return Number(result?.numDeletedRows)||0}static async rotateToken(oldToken){const{findToken:findRawToken,revokeToken:revokeRawToken}=await import("./tokens"),existing=await findRawToken(oldToken);if(!existing)return null;const remainingMs=existing.expiresAt?existing.expiresAt.getTime()-Date.now():config.auth.tokenExpiry??3600000,expiresInMinutes=Math.max(1,Math.floor(remainingMs/60000));await revokeRawToken(oldToken);const user=await User.find(existing.userId);if(!user)return null;return(await this.createTokenForUser(user,{name:existing.name,abilities:existing.scopes??["*"],expiresInMinutes,withRefreshToken:!1})).plainTextToken}static async findToken(tokenId){return this.getTokenFromId(tokenId)}static async once(credentials){const username=config.auth.username||"email",password=config.auth.password||"password",email=credentials[username];if(!email)return!1;const user=await User.where("email","=",email).first(),authPass=credentials[password]||"",hashToVerify=user?.password||DUMMY_BCRYPT_HASH;if(await verifyHash(authPass,hashToVerify)&&user){const state=authStateOrNull();if(state)state.authUser=user;return!0}return!1}static guard(_name){return this}static viaRemember(){return!1}static clearState(){const state=authStateOrNull();if(state){state.authUser=void 0;state.currentToken=void 0;state.clientSecret=void 0}}}
|
|
1
|
+
import{config}from"@stacksjs/config";import{db,parseSqlDateTime}from"@stacksjs/database";import{HttpError}from"@stacksjs/error-handling";import{formatDate,User}from"@stacksjs/orm";import{getCurrentRequest,request}from"@stacksjs/router";import{requestToken}from"./request-token";import{Buffer}from"node:buffer";import{createHash,randomBytes,timingSafeEqual}from"node:crypto";import{decrypt,encrypt,verifyHash}from"@stacksjs/security";import{log}from"@stacksjs/logging";import{DUMMY_BCRYPT_HASH}from"./internal-constants";import{RateLimiter}from"./rate-limiter";const REQUEST_AUTH_STATE_KEY=Symbol.for("stacks.requestAuthState");function authStateOrNull(){const req=getCurrentRequest();if(!req)return null;let state=req[REQUEST_AUTH_STATE_KEY];if(!state){state={};req[REQUEST_AUTH_STATE_KEY]=state}return state}function hashToken(token){return createHash("sha256").update(token).digest("hex")}import{createToken as createRawToken,getPasswordChangedAt,isIssuedBeforePasswordChange,parseScopes}from"./tokens";export class Auth{static getBearerToken(){return requestToken(request)}static parseToken(token){const firstColonIndex=token.indexOf(":");if(firstColonIndex===-1)return null;const plainToken=token.substring(0,firstColonIndex),encryptedId=token.substring(firstColonIndex+1);if(!plainToken||!encryptedId)return null;return{plainToken,encryptedId}}static async getClientSecret(){const state=authStateOrNull();if(state?.clientSecret)return state.clientSecret;const client=await this.getPersonalAccessClient();if(state)state.clientSecret=client.secret;return client.secret}static async encryptTokenId(id){return await encrypt(String(id))}static async decryptTokenId(encryptedId){try{return await decrypt(encryptedId)}catch{try{const clientSecret=await this.getClientSecret();return await decrypt(encryptedId,clientSecret)}catch{return null}}}static async getPersonalAccessClient(){try{const client=await db.selectFrom("oauth_clients").where("personal_access_client","=",!0).where("revoked","=",!1).selectAll().executeTakeFirst();if(!client)throw new HttpError(500,"No personal access client found. Please run `./buddy auth:setup` first.");return client}catch(error){if(error instanceof Error&&error.message.includes("does not exist"))throw new HttpError(500,"OAuth tables not found. Please run `./buddy auth:setup` first.");throw error}}static async validateClient(clientId,clientSecret){const client=await db.selectFrom("oauth_clients").where("id","=",clientId).where("revoked","=",!1).selectAll().executeTakeFirst(),provided=Buffer.from(clientSecret);if(!client?.secret){const dummy=Buffer.alloc(Math.max(provided.length,1)),padded=provided.length>0?provided:Buffer.alloc(1);timingSafeEqual(dummy,padded);return!1}const stored=String(client.secret);if(stored.startsWith("$2"))return await verifyHash(clientSecret,stored);const storedBuf=Buffer.from(stored);if(storedBuf.length!==provided.length){timingSafeEqual(storedBuf,storedBuf);return!1}return timingSafeEqual(storedBuf,provided)}static async getTokenFromId(tokenId){const result=await db.selectFrom("oauth_access_tokens").where("id","=",tokenId).selectAll().executeTakeFirst();if(!result)return null;const token=result;return{id:token.id,userId:token.user_id,clientId:token.oauth_client_id,name:token.name||"auth-token",scopes:parseScopes(token.scopes),abilities:parseScopes(token.scopes),expiresAt:parseSqlDateTime(token.expires_at),createdAt:token.created_at?new Date(String(token.created_at)):new Date,updatedAt:token.updated_at?new Date(String(token.updated_at)):new Date,revoked:!!token.revoked}}static async attempt(credentials){const username=config.auth.username||"email",password=config.auth.password||"password",email=credentials[username];if(!email)return!1;const isRateLimited=await RateLimiter.isRateLimited(email),user=await User.where("email","=",email).first(),authPass=credentials[password]||"",hashToVerify=user?.password||DUMMY_BCRYPT_HASH,hashCheck=await verifyHash(authPass,hashToVerify);if(isRateLimited)return!1;if(hashCheck&&user){await RateLimiter.resetAttempts(email);const state=authStateOrNull();if(state)state.authUser=user;return!0}await RateLimiter.recordFailedAttempt(email);return!1}static async validate(credentials){const username=config.auth.username||"email",password=config.auth.password||"password",email=credentials[username];if(!email)return!1;const user=await User.where("email","=",email).first(),authPass=credentials[password]||"",hashToVerify=user?.password||DUMMY_BCRYPT_HASH;return await verifyHash(authPass,hashToVerify)&&!!user}static async login(credentials,options){if(!await this.attempt(credentials))return null;const username=config.auth.username||"email",usernameValue=credentials[username];if(usernameValue===void 0)return null;const authedUser=authStateOrNull()?.authUser??await User.where(username,"=",usernameValue).first();if(!authedUser)return null;const{plainTextToken,refreshToken,expiresIn}=await this.createTokenForUser(authedUser,options);return{user:authedUser,token:plainTextToken,refreshToken,expiresIn}}static async loginUsingId(userId,options){const user=await User.find(userId);if(!user)return null;const state=authStateOrNull();if(state)state.authUser=user;const{plainTextToken,refreshToken,expiresIn}=await this.createTokenForUser(user,options);return{user,token:plainTextToken,refreshToken,expiresIn}}static async logout(){const bearerToken=this.getBearerToken();if(bearerToken)await this.revokeToken(bearerToken);const state=authStateOrNull();if(state){state.authUser=void 0;state.currentToken=void 0}}static async user(){const state=authStateOrNull();if(state?.authUser)return state.authUser;const bearerToken=this.getBearerToken();if(!bearerToken)return;const user=await this.getUserFromToken(bearerToken);if(user&&state)state.authUser=user;return user}static async check(){return await this.user()!==void 0}static async guest(){return!await this.check()}static async id(){return(await this.user())?.id}static setUser(user){const state=authStateOrNull();if(state)state.authUser=user}static async createTokenForUser(user,options){const name=options?.name??config.auth.defaultTokenName??"auth-token",abilities=options?.abilities??options?.scopes??config.auth.defaultAbilities??["*"],accessTtlMs=options?.expiresInMinutes!==void 0?options.expiresInMinutes*60*1000:config.auth.tokenExpiry??3600000,expiresAt=options?.expiresAt??new Date(Date.now()+accessTtlMs),expiresInMinutes=Math.max(1,Math.floor((expiresAt.getTime()-Date.now())/60000)),refreshExpiresInDays=options?.refreshExpiresInDays??Math.max(1,Math.round((config.auth.refreshTokenExpiry??2592000000)/86400000));log.debug(`[auth] Creating token for user#${user.id}: ${name}`);const result=await createRawToken(user.id,name,abilities,{expiresInMinutes,withRefreshToken:options?.withRefreshToken!==!1,refreshExpiresInDays,userAgent:options?.userAgent??null,ipAddress:options?.ipAddress??null}),plainTextToken=result.plainTextToken;return{accessToken:{id:result.accessToken.id,userId:result.accessToken.userId,clientId:result.accessToken.clientId,name:result.accessToken.name,scopes:result.accessToken.scopes,abilities,expiresAt:result.accessToken.expiresAt??expiresAt,createdAt:result.accessToken.createdAt,updatedAt:result.accessToken.updatedAt,revoked:result.accessToken.revoked,plainTextToken},plainTextToken,refreshToken:result.refreshToken,expiresIn:result.expiresIn}}static async createToken(user,name=config.auth.defaultTokenName||"auth-token",abilities=config.auth.defaultAbilities||["*"]){const{plainTextToken}=await this.createTokenForUser(user,{name,abilities});return plainTextToken}static async requestToken(credentials,clientId,clientSecret){if(!await this.validateClient(clientId,clientSecret))throw new HttpError(401,"Invalid client credentials");const isValid=await this.attempt(credentials),authedUser=authStateOrNull()?.authUser;if(!isValid||!authedUser)return null;return{token:await this.createToken(authedUser,"user-auth-token")}}static async validateToken(token){const hashedPlainToken=hashToken(token),accessToken=await db.selectFrom("oauth_access_tokens").where("token","=",hashedPlainToken).selectAll().executeTakeFirst();if(!accessToken)return!1;log.debug(`[auth] Token validated for token#${accessToken.id}`);if(accessToken.expires_at&&(parseSqlDateTime(accessToken.expires_at)??new Date(0))<new Date){await db.deleteFrom("oauth_access_tokens").where("id","=",accessToken.id).execute();return!1}if(accessToken.revoked)return!1;if(isIssuedBeforePasswordChange(accessToken.created_at,await getPasswordChangedAt(accessToken.user_id)))return!1;await db.updateTable("oauth_access_tokens").set({updated_at:formatDate(new Date)}).where("id","=",accessToken.id).execute();return!0}static async getUserFromToken(token){const hashedPlainToken=hashToken(token),accessToken=await db.selectFrom("oauth_access_tokens").where("token","=",hashedPlainToken).selectAll().executeTakeFirst();if(!accessToken)return;if(accessToken.expires_at&&(parseSqlDateTime(accessToken.expires_at)??new Date(0))<new Date){await db.deleteFrom("oauth_access_tokens").where("id","=",accessToken.id).execute();return}if(accessToken.revoked)return;const idleMs=config.auth?.idleTimeout??0;if(idleMs>0){const lastSeen=parseSqlDateTime(accessToken.updated_at??accessToken.created_at);if(lastSeen&&Date.now()-lastSeen.getTime()>idleMs){await db.updateTable("oauth_access_tokens").set({revoked:!0}).where("id","=",accessToken.id).execute();return}}const stateForToken=authStateOrNull();if(stateForToken)stateForToken.currentToken=await this.getTokenFromId(accessToken.id)??void 0;await db.updateTable("oauth_access_tokens").set({updated_at:formatDate(new Date)}).where("id","=",accessToken.id).execute();if(!accessToken?.user_id)return;const user=await User.find(accessToken.user_id);if(isIssuedBeforePasswordChange(accessToken.created_at,await getPasswordChangedAt(accessToken.user_id)))return;return user}static async currentAccessToken(){const state=authStateOrNull();if(state?.currentToken)return state.currentToken;const bearerToken=this.getBearerToken();if(!bearerToken)return;const accessToken=await db.selectFrom("oauth_access_tokens").where("token","=",hashToken(bearerToken)).select(["id"]).executeTakeFirst();if(!accessToken)return;const token=await this.getTokenFromId(Number(accessToken.id));if(token&&state)state.currentToken=token;return token??void 0}static async tokenCan(ability){const token=await this.currentAccessToken();if(!token)return!1;if(token.abilities.includes("*"))return!0;return token.abilities.includes(ability)}static async tokenCant(ability){return!await this.tokenCan(ability)}static async tokenAbilities(){return(await this.currentAccessToken())?.abilities??[]}static async tokenCanAll(abilities){const token=await this.currentAccessToken();if(!token)return!1;if(token.abilities.includes("*"))return!0;return abilities.every((a)=>token.abilities.includes(a))}static async tokenCanAny(abilities){const token=await this.currentAccessToken();if(!token)return!1;if(token.abilities.includes("*"))return!0;return abilities.some((a)=>token.abilities.includes(a))}static async tokens(userId){const uid=userId??await this.id();if(!uid)return[];return(await db.selectFrom("oauth_access_tokens").where("user_id","=",uid).where("revoked","=",!1).selectAll().execute()).map((token)=>({id:Number(token.id),userId:Number(token.user_id),clientId:Number(token.oauth_client_id),name:String(token.name||"auth-token"),scopes:parseScopes(String(token.scopes??"")),abilities:parseScopes(String(token.scopes??"")),expiresAt:parseSqlDateTime(token.expires_at),createdAt:token.created_at?new Date(String(token.created_at)):new Date,updatedAt:token.updated_at?new Date(String(token.updated_at)):new Date,revoked:!!token.revoked}))}static async revokeToken(token){const accessToken=await db.selectFrom("oauth_access_tokens").where("token","=",hashToken(token)).select(["id"]).executeTakeFirst();if(accessToken)await this.revokeRefreshTokensFor(Number(accessToken.id));await db.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:formatDate(new Date)}).where("token","=",hashToken(token)).execute()}static async revokeTokenById(tokenId){await this.revokeRefreshTokensFor(tokenId);await db.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:formatDate(new Date)}).where("id","=",tokenId).execute()}static async revokeRefreshTokensFor(accessTokenId){await db.updateTable("oauth_refresh_tokens").set({revoked:!0}).where("access_token_id","=",accessTokenId).execute()}static async revokeAllTokens(userId){const uid=userId??await this.id();if(!uid)return;const accessTokens=await db.selectFrom("oauth_access_tokens").where("user_id","=",uid).select(["id"]).execute();for(const accessToken of accessTokens)await this.revokeRefreshTokensFor(Number(accessToken.id));await db.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:formatDate(new Date)}).where("user_id","=",uid).execute()}static async revokeOtherTokens(userId){const uid=userId??await this.id();if(!uid)return;const currentToken=await this.currentAccessToken();if(!currentToken)return;await db.updateTable("oauth_access_tokens").set({revoked:!0,updated_at:formatDate(new Date)}).where("user_id","=",uid).where("id","!=",currentToken.id).execute()}static async pruneExpiredTokens(){const result=await db.deleteFrom("oauth_access_tokens").where("expires_at","<",formatDate(new Date)).executeTakeFirst();return Number(result?.numDeletedRows)||0}static async pruneRevokedTokens(){const result=await db.deleteFrom("oauth_access_tokens").where("revoked","=",!0).executeTakeFirst();return Number(result?.numDeletedRows)||0}static async rotateToken(oldToken){const{findToken:findRawToken,revokeToken:revokeRawToken}=await import("./tokens"),existing=await findRawToken(oldToken);if(!existing)return null;const remainingMs=existing.expiresAt?existing.expiresAt.getTime()-Date.now():config.auth.tokenExpiry??3600000,expiresInMinutes=Math.max(1,Math.floor(remainingMs/60000));await revokeRawToken(oldToken);const user=await User.find(existing.userId);if(!user)return null;return(await this.createTokenForUser(user,{name:existing.name,abilities:existing.scopes??["*"],expiresInMinutes,withRefreshToken:!1})).plainTextToken}static async findToken(tokenId){return this.getTokenFromId(tokenId)}static async once(credentials){const username=config.auth.username||"email",password=config.auth.password||"password",email=credentials[username];if(!email)return!1;const user=await User.where("email","=",email).first(),authPass=credentials[password]||"",hashToVerify=user?.password||DUMMY_BCRYPT_HASH;if(await verifyHash(authPass,hashToVerify)&&user){const state=authStateOrNull();if(state)state.authUser=user;return!0}return!1}static guard(_name){return this}static viaRemember(){return!1}static clearState(){const state=authStateOrNull();if(state){state.authUser=void 0;state.currentToken=void 0;state.clientSecret=void 0}}}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/auth",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.370",
|
|
6
6
|
"description": "A more simplistic way to authenticate.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"better-dx": "^0.2.17",
|
|
64
|
-
"@stacksjs/error-handling": "0.70.
|
|
65
|
-
"@stacksjs/router": "0.70.
|
|
64
|
+
"@stacksjs/error-handling": "0.70.370",
|
|
65
|
+
"@stacksjs/router": "0.70.370"
|
|
66
66
|
}
|
|
67
67
|
}
|