mirror of
https://github.com/Haidra-Org/AI-Horde.git
synced 2026-11-06 05:17:31 +00:00
1 parent
e547e1599f
commit
8ca3849713
12 files changed
+306
-16
No files matched your search
@@ -1,5 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
# 4.12.0
|
||||
|
||||
**Added Shared Keys**. Now each user can generate a number of shared keys which they can give to others to use.
|
||||
When using a shared key to generate, the request pretend act as if it was that user.
|
||||
|
||||
However shared keys cannot be used for any other purpose than generating, so they cannot be abused. They are thus meant to be a lower-security option for sharing one's priority, without having to transfer kudos all the time.
|
||||
|
||||
Shared keys can be created with an optional limited amount of kudos to use, and/or an expiry date.
|
||||
Regardless of what the shared key kudos limit is, the request will use the full kudos priority of its owner.
|
||||
|
||||
New Endpoints:
|
||||
|
||||
* /v2/sharedkeys PUT
|
||||
* /v2/sharedkeys GET/PATCH/DELETE
|
||||
|
||||
Check api documentation for payloads required.
|
||||
|
||||
* Shortened limiter for kudos transfers to avoid abuse
|
||||
|
||||
# 4.11.2
|
||||
|
||||
* Increased stipend for moderators
|
||||
|
||||
+17
-1
@@ -183,6 +183,21 @@ class Models:
|
||||
"awarded": fields.Float(default=0,description="The amount of Kudos this user has been awarded from things like rating images."),
|
||||
})
|
||||
|
||||
|
||||
self.input_model_sharedkey = api.model('SharedKeyInput', {
|
||||
"kudos": fields.Integer(min=-1, max=50000000, default=5000, required=False, description="The Kudos limit assigned to this key. If -1, then anyone with this key can use an unlimited amount of kudos from this account."),
|
||||
"expiry": fields.Integer(min=-1, default=-1, example=30, required=False, description="The amount of days after which this key will expire. If -1, this key will not expire"),
|
||||
"name": fields.String(min_length=3, max_length=255, required=False, example="Mutual Aid", description="A descriptive name for this key"),
|
||||
})
|
||||
|
||||
self.response_model_sharedkey_details = api.model('SharedKeyDetails', {
|
||||
"id": fields.String(description="The SharedKey ID"),
|
||||
"username": fields.String(description="The owning user's unique Username. It is a combination of their chosen alias plus their ID."),
|
||||
"kudos": fields.Integer(description="The Kudos limit assigned to this key"),
|
||||
"expiry": fields.DateTime(dt_format='rfc822',description="The date at which this API key will expire."),
|
||||
"utilized": fields.Integer(description="How mych kudos has been utilized via this shared key until now."),
|
||||
})
|
||||
|
||||
#TODO: Obsolete
|
||||
self.response_model_contrib_details = api.model('ContributionsDetails', {
|
||||
"megapixelsteps": fields.Float(description="How many megapixelsteps this user has generated"),
|
||||
@@ -227,7 +242,8 @@ class Models:
|
||||
"moderator": fields.Boolean(example=False,description="This user is a Horde moderator."),
|
||||
"kudos_details": fields.Nested(self.response_model_user_kudos_details),
|
||||
"worker_count": fields.Integer(description="How many workers this user has created (active or inactive)"),
|
||||
"worker_ids": fields.List(fields.String(description="Privileged or public when the user has explicitly allows it to be public.")),
|
||||
"worker_ids": fields.List(fields.String(description="Privileged or public when the user has explicitly allows it to be public.", example="00000000-0000-0000-0000-000000000000")),
|
||||
"sharedkey_ids": fields.List(fields.String(description="(Privileged) The list of shared key IDs created by this user.", example="00000000-0000-0000-0000-000000000000")),
|
||||
"monthly_kudos": fields.Nested(self.response_model_monthly_kudos, skip_none=True),
|
||||
"trusted": fields.Boolean(example=False,description="This user is a trusted member of the Horde."),
|
||||
"flagged": fields.Boolean(example=False,description="This user has been flagged for suspicious activity."),
|
||||
|
||||
@@ -16,6 +16,8 @@ api.add_resource(kobold.TextJobSubmit, "/generate/text/submit")
|
||||
api.add_resource(base.Users, "/users")
|
||||
api.add_resource(base.UserSingle, "/users/<string:user_id>")
|
||||
api.add_resource(base.FindUser, "/find_user")
|
||||
api.add_resource(base.SharedKey, "/sharedkeys")
|
||||
api.add_resource(base.SharedKeySingle, "/sharedkeys/<string:sharedkey_id>")
|
||||
api.add_resource(base.Workers, "/workers")
|
||||
api.add_resource(base.WorkerSingle, "/workers/<string:worker_id>")
|
||||
api.add_resource(base.TransferKudos, "/kudos/transfer")
|
||||
|
||||
+146
-5
@@ -17,7 +17,7 @@ from horde.limiter import limiter
|
||||
from horde.logger import logger
|
||||
from horde.argparser import args
|
||||
from horde import exceptions as e
|
||||
from horde.classes.base.user import User
|
||||
from horde.classes.base.user import User, UserSharedKey
|
||||
from horde.classes.base.waiting_prompt import WaitingPrompt
|
||||
from horde.classes.base.worker import Worker
|
||||
import horde.classes.base.stats as stats
|
||||
@@ -147,7 +147,14 @@ class GenerateTemplate(Resource):
|
||||
with HORDE.app_context(): # TODO DOUBLE CHECK THIS
|
||||
#logger.warning(datetime.utcnow())
|
||||
if self.args.apikey:
|
||||
self.user = database.find_user_by_api_key(self.args['apikey'])
|
||||
self.sharedkey = database.find_sharedkey(self.args.apikey)
|
||||
if self.sharedkey:
|
||||
is_valid, error_msg = self.sharedkey.is_valid()
|
||||
if not is_valid:
|
||||
raise e.Forbidden(error_msg)
|
||||
self.user = self.sharedkey.user
|
||||
if not self.user:
|
||||
self.user = database.find_user_by_api_key(self.args.apikey)
|
||||
#logger.warning(datetime.utcnow())
|
||||
if not self.user:
|
||||
raise e.InvalidAPIKey('generation')
|
||||
@@ -244,6 +251,7 @@ class GenerateTemplate(Resource):
|
||||
censor_nsfw = self.args.censor_nsfw,
|
||||
trusted_workers = self.args.trusted_workers,
|
||||
ipaddr = self.user_ip,
|
||||
sharedkey_id = self.args.apikey if self.sharedkey else None,
|
||||
)
|
||||
|
||||
# We split this into its own function, so that it may be overriden and extended
|
||||
@@ -493,6 +501,7 @@ class TransferKudos(Resource):
|
||||
parser.add_argument("username", type=str, required=True, help="The user ID which will receive the kudos", location="json")
|
||||
parser.add_argument("amount", type=int, required=False, default=100, help="The amount of kudos to transfer", location="json")
|
||||
|
||||
decorators = [limiter.limit("3/second", key_func = get_request_path)]
|
||||
@api.expect(parser)
|
||||
@api.marshal_with(models.response_model_kudos_transfer, code=200, description='Kudos Transferred')
|
||||
@api.response(400, 'Validation Error', models.response_model_error)
|
||||
@@ -1004,10 +1013,22 @@ class FindUser(Resource):
|
||||
if cached_user:
|
||||
user_details = json.loads(cached_user)
|
||||
else:
|
||||
user = database.find_user_by_api_key(self.args.apikey)
|
||||
user = database.find_user_by_sharedkey(self.args.apikey)
|
||||
sharedkey = True
|
||||
privilege = 0
|
||||
if not user:
|
||||
user = database.find_user_by_api_key(self.args.apikey)
|
||||
sharedkey = False
|
||||
privilege = 1
|
||||
if not user:
|
||||
raise e.UserNotFound(self.args.apikey, 'api_key')
|
||||
user_details = user.get_details(1)
|
||||
user_details = user.get_details(privilege)
|
||||
if sharedkey:
|
||||
sk = database.find_sharedkey(self.args.apikey)
|
||||
skname = ''
|
||||
if sk.name is not None:
|
||||
skname = f": {sk.name}"
|
||||
user_details["username"] = user_details["username"] + f" (Shared Key{skname})"
|
||||
if hr.horde_r:
|
||||
hr.horde_r_setex_json(cache_name, timedelta(seconds=300), user_details)
|
||||
return(user_details,200)
|
||||
@@ -1580,4 +1601,124 @@ class Heartbeat(Resource):
|
||||
'message': 'OK',
|
||||
'version': HORDE_VERSION,
|
||||
},200
|
||||
|
||||
|
||||
|
||||
class SharedKey(Resource):
|
||||
put_parser = reqparse.RequestParser()
|
||||
put_parser.add_argument("apikey", type=str, required=True, help="User API key", location='headers')
|
||||
put_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version", location="headers")
|
||||
put_parser.add_argument("kudos", type=int, required=False, default=5000, help="The amount of kudos limit available to this key", location="json")
|
||||
put_parser.add_argument("expiry", type=int, required=False, default=-1, help="The amount of days which this key will stay active.", location="json")
|
||||
put_parser.add_argument("name", type=str, required=False, help="A descriptive name for this key", location="json")
|
||||
|
||||
decorators = [limiter.limit("5/minute", key_func = get_request_path)]
|
||||
@api.expect(put_parser, models.input_model_sharedkey)
|
||||
@api.marshal_with(models.response_model_sharedkey_details, code=200, description='SharedKey Details', skip_none=True)
|
||||
@api.response(400, 'Validation Error', models.response_model_error)
|
||||
@api.response(401, 'Invalid API Key', models.response_model_error)
|
||||
@api.response(403, 'Access Denied', models.response_model_error)
|
||||
@api.response(404, 'Shared Key Not Found', models.response_model_error)
|
||||
def put(self):
|
||||
'''Create a new SharedKey for this user
|
||||
'''
|
||||
self.args = self.put_parser.parse_args()
|
||||
user: User = database.find_user_by_api_key(self.args.apikey)
|
||||
if not user:
|
||||
raise e.InvalidAPIKey("get sharedkey")
|
||||
if user.is_anon():
|
||||
raise e.AnonForbidden
|
||||
if user.count_sharedkeys() > user.max_sharedkeys():
|
||||
raise e.Forbidden(f"You cannot have more than {user.max_sharedkeys()} shared keys.")
|
||||
expiry = None
|
||||
if self.args.expiry and self.args.expiry != -1:
|
||||
expiry = datetime.utcnow() + timedelta(days=self.args.expiry)
|
||||
new_key = UserSharedKey(
|
||||
user_id = user.id,
|
||||
kudos = self.args.kudos,
|
||||
expiry = expiry,
|
||||
name = self.args.name,
|
||||
)
|
||||
db.session.add(new_key)
|
||||
db.session.commit()
|
||||
return new_key.get_details(),200
|
||||
|
||||
|
||||
class SharedKeySingle(Resource):
|
||||
get_parser = reqparse.RequestParser()
|
||||
get_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version", location="headers")
|
||||
|
||||
@cache.cached(timeout=60)
|
||||
@api.expect(get_parser)
|
||||
@api.marshal_with(models.response_model_sharedkey_details, code=200, description='Shared Key Details', skip_none=True)
|
||||
@api.response(404, 'Shared Key Not Found', models.response_model_error)
|
||||
def get(self, sharedkey_id=''):
|
||||
'''Get details about an existing Shared Key for this user
|
||||
'''
|
||||
self.args = self.get_parser.parse_args()
|
||||
sharedkey = database.find_sharedkey(sharedkey_id)
|
||||
if not sharedkey:
|
||||
raise e.InvalidAPIKey("get sharedkey", keytype="Shared")
|
||||
return sharedkey.get_details(),200
|
||||
|
||||
patch_parser = reqparse.RequestParser()
|
||||
patch_parser.add_argument("apikey", type=str, required=True, help="User API key", location='headers')
|
||||
patch_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version", location="headers")
|
||||
patch_parser.add_argument("kudos", type=int, required=False, help="The amount of kudos limit available to this key", location="json")
|
||||
patch_parser.add_argument("expiry", type=int, required=False, help="The amount of days from today which this key will stay active.", location="json")
|
||||
patch_parser.add_argument("name", type=str, required=False, help="A descriptive name for this key", location="json")
|
||||
|
||||
@api.expect(patch_parser, models.input_model_sharedkey)
|
||||
@api.marshal_with(models.response_model_sharedkey_details, code=200, description='Shared Key Details', skip_none=True)
|
||||
@api.response(400, 'Validation Error', models.response_model_error)
|
||||
@api.response(401, 'Invalid API Key', models.response_model_error)
|
||||
@api.response(403, 'Access Denied', models.response_model_error)
|
||||
@api.response(404, 'Shared Key Not Found', models.response_model_error)
|
||||
def patch(self, sharedkey_id=''):
|
||||
'''Modify an existing Shared Key
|
||||
'''
|
||||
self.args = self.patch_parser.parse_args()
|
||||
sharedkey = database.find_sharedkey(sharedkey_id)
|
||||
if not sharedkey:
|
||||
raise e.InvalidAPIKey("Shared Key Not Found.", keytype="Shared")
|
||||
user = database.find_user_by_api_key(self.args.apikey)
|
||||
if not user:
|
||||
raise e.InvalidAPIKey("patch sharedkey")
|
||||
if sharedkey.user_id != user.id:
|
||||
raise e.Forbidden(f"Shared Key {sharedkey.id} belongs to {sharedkey.user.get_unique_alias()} and not to {user.get_unique_alias()}.")
|
||||
if not self.args.expiry and not self.args.kudos and not self.arg.name:
|
||||
raise e.NoValidActions("No shared key modification selected!")
|
||||
if self.args.expiry is not None:
|
||||
if self.args.expiry == -1:
|
||||
sharedkey.expiry = None
|
||||
else:
|
||||
sharedkey.expiry = datetime.utcnow() + timedelta(days=self.args.expiry)
|
||||
if self.args.kudos is not None:
|
||||
sharedkey.kudos = self.args.kudos
|
||||
if self.args.name is not None:
|
||||
sharedkey.name = self.args.name
|
||||
db.session.commit()
|
||||
return sharedkey.get_details(),200
|
||||
|
||||
delete_parser = reqparse.RequestParser()
|
||||
delete_parser.add_argument("apikey", type=str, required=True, help="User API key", location='headers')
|
||||
delete_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version", location="headers")
|
||||
|
||||
@api.expect(delete_parser)
|
||||
@api.marshal_with(models.response_model_simple_response, code=200, description='Shared Key Deleted')
|
||||
@api.response(404, 'Shared Key Not Found', models.response_model_error)
|
||||
def delete(self, sharedkey_id=''):
|
||||
'''Delete an existing SharedKey for this user
|
||||
'''
|
||||
self.args = self.delete_parser.parse_args()
|
||||
sharedkey = database.find_sharedkey(sharedkey_id)
|
||||
if not sharedkey:
|
||||
raise e.InvalidAPIKey("Shared Key Not Found.", keytype="Shared")
|
||||
user = database.find_user_by_api_key(self.args.apikey)
|
||||
if not user:
|
||||
raise e.InvalidAPIKey("delete sharedkey")
|
||||
if sharedkey.user_id != user.id:
|
||||
raise e.Forbidden(f"Shared Key {sharedkey.id} belongs to {sharedkey.user.get_unique_alias()} and not to {user.get_unique_alias()}.")
|
||||
db.session.delete(sharedkey)
|
||||
db.session.commit()
|
||||
return {"message": "OK"},200
|
||||
|
||||
@@ -51,8 +51,16 @@ class TextAsyncGenerate(GenerateTemplate):
|
||||
ipaddr = self.user_ip,
|
||||
safe_ip=True,
|
||||
client_agent=self.args["Client-Agent"],
|
||||
sharedkey_id = self.args.apikey if self.sharedkey else None,
|
||||
)
|
||||
_, total_threads = database.count_active_workers("text")
|
||||
required_kudos = round(self.wp.max_length * highest_multiplier / 21, 2) * self.wp.n
|
||||
if self.sharedkey and required_kudos > self.sharedkey.kudos:
|
||||
raise e.KudosUpfront(
|
||||
required_kudos,
|
||||
self.username,
|
||||
message=f"This shared key does not have enough remaining kudos ({self.sharedkey.kudos}) to fulfill this reques ({required_kudos})."
|
||||
)
|
||||
needs_kudos, tokens = self.wp.require_upfront_kudos(database.retrieve_totals(),total_threads)
|
||||
if needs_kudos:
|
||||
if len(self.models) == 0:
|
||||
@@ -65,7 +73,6 @@ class TextAsyncGenerate(GenerateTemplate):
|
||||
model_multiplier = model_reference.get_text_model_multiplier(model)
|
||||
if model_multiplier > highest_multiplier:
|
||||
highest_multiplier = model_multiplier
|
||||
required_kudos = round(self.wp.max_length * highest_multiplier / 21, 2) * self.wp.n
|
||||
if required_kudos > self.user.kudos:
|
||||
raise e.KudosUpfront(
|
||||
required_kudos,
|
||||
|
||||
@@ -145,11 +145,18 @@ class ImageAsyncGenerate(GenerateTemplate):
|
||||
r2=self.args.r2,
|
||||
shared=shared,
|
||||
client_agent=self.args["Client-Agent"],
|
||||
sharedkey_id = self.args.apikey if self.sharedkey else None,
|
||||
)
|
||||
_, total_threads = database.count_active_workers("image")
|
||||
required_kudos = self.wp.kudos * self.wp.n
|
||||
if self.sharedkey and required_kudos > self.sharedkey.kudos:
|
||||
raise e.KudosUpfront(
|
||||
required_kudos,
|
||||
self.username,
|
||||
message=f"This shared key does not have enough remaining kudos ({self.sharedkey.kudos}) to fulfill this reques ({required_kudos})."
|
||||
)
|
||||
needs_kudos,resolution = self.wp.require_upfront_kudos(database.retrieve_totals(),total_threads)
|
||||
if needs_kudos:
|
||||
required_kudos = self.wp.kudos * self.wp.n
|
||||
if required_kudos > self.user.kudos:
|
||||
raise e.KudosUpfront(
|
||||
required_kudos,
|
||||
|
||||
@@ -3,17 +3,21 @@ import os
|
||||
|
||||
import dateutil.relativedelta
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Enum, UniqueConstraint,exists, and_
|
||||
from sqlalchemy import Enum, UniqueConstraint
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from horde.logger import logger
|
||||
from horde.flask import db
|
||||
from horde.flask import db, SQLITE_MODE
|
||||
from horde.vars import thing_name, text_thing_divisor, text_thing_name
|
||||
from horde import vars as hv
|
||||
from horde.suspicions import Suspicions, SUSPICION_LOGS
|
||||
from horde.utils import is_profane, sanitize_string, generate_client_id
|
||||
from horde.patreon import patrons
|
||||
from horde.enums import UserRecordTypes, UserRoleTypes
|
||||
from horde.utils import get_db_uuid
|
||||
|
||||
uuid_column_type = lambda: UUID(as_uuid=True) if not SQLITE_MODE else db.String(36)
|
||||
|
||||
|
||||
class UserStats(db.Model):
|
||||
@@ -54,6 +58,46 @@ class UserRole(db.Model):
|
||||
user_role = db.Column(Enum(UserRoleTypes), nullable=False)
|
||||
value = db.Column(db.Boolean, default=False, nullable=False)
|
||||
|
||||
class UserSharedKey(db.Model):
|
||||
__tablename__ = "user_sharedkeys"
|
||||
id = db.Column(uuid_column_type(), primary_key=True, default=get_db_uuid)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
user = db.relationship("User", back_populates="sharedkeys")
|
||||
kudos = db.Column(db.BigInteger, default=5000, nullable=False)
|
||||
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||
expiry = db.Column(db.DateTime, index=True)
|
||||
name = db.Column(db.String(255), nullable=True)
|
||||
utilized = db.Column(db.BigInteger, default=0, nullable=False)
|
||||
waiting_prompts = db.relationship("WaitingPrompt", back_populates="sharedkey", passive_deletes=True, cascade="all, delete-orphan")
|
||||
|
||||
@logger.catch(reraise=True)
|
||||
def get_details(self):
|
||||
ret_dict = {
|
||||
"username": self.user.get_unique_alias(),
|
||||
"id": self.id,
|
||||
"kudos": self.kudos,
|
||||
"expiry": self.expiry,
|
||||
"utilized": self.utilized,
|
||||
}
|
||||
return ret_dict
|
||||
|
||||
def consume_kudos(self, kudos):
|
||||
if self.kudos == 0:
|
||||
return
|
||||
if self.kudos != -1:
|
||||
self.kudos = round(self.kudos - kudos, 2)
|
||||
self.utilized = round(self.utilized + kudos, 2)
|
||||
logger.debug(f"Utilized {kudos} from shared key {self.id}. {self.kudos} remaining.")
|
||||
db.session.commit()
|
||||
|
||||
def is_valid(self):
|
||||
if self.kudos == 0:
|
||||
return False,"This shared key has run out of kudos."
|
||||
if self.expiry is not None and self.expiry < datetime.utcnow():
|
||||
return False,"This shared key has expired"
|
||||
else:
|
||||
return True, None
|
||||
|
||||
class User(db.Model):
|
||||
__tablename__ = "users"
|
||||
SUSPICION_THRESHOLD = 5
|
||||
@@ -81,6 +125,7 @@ class User(db.Model):
|
||||
|
||||
workers = db.relationship(f"Worker", back_populates="user", cascade="all, delete-orphan")
|
||||
teams = db.relationship(f"Team", back_populates="owner", cascade="all, delete-orphan")
|
||||
sharedkeys = db.relationship(f"UserSharedKey", back_populates="user", cascade="all, delete-orphan")
|
||||
suspicions = db.relationship("UserSuspicions", back_populates="user", cascade="all, delete-orphan")
|
||||
records = db.relationship("UserRecords", back_populates="user", cascade="all, delete-orphan")
|
||||
roles = db.relationship("UserRole", back_populates="user", cascade="all, delete-orphan")
|
||||
@@ -511,6 +556,14 @@ class User(db.Model):
|
||||
def count_workers(self):
|
||||
return(len(self.workers))
|
||||
|
||||
def count_sharedkeys(self):
|
||||
return(len(self.sharedkeys))
|
||||
|
||||
def max_sharedkeys(self):
|
||||
if self.trusted:
|
||||
return 10
|
||||
return 3
|
||||
|
||||
def is_suspicious(self):
|
||||
if self.trusted:
|
||||
return(False)
|
||||
@@ -599,6 +652,11 @@ class User(db.Model):
|
||||
workers_array.append(str(worker.id))
|
||||
ret_dict["worker_ids"] = workers_array
|
||||
ret_dict['contact'] = self.contact
|
||||
if details_privilege >= 1:
|
||||
sharedkeys_array = []
|
||||
for sk in self.sharedkeys:
|
||||
sharedkeys_array.append(str(sk.id))
|
||||
ret_dict["sharedkey_ids"] = sharedkeys_array
|
||||
if details_privilege >= 2:
|
||||
mk_dict = {
|
||||
"amount": self.calculate_monthly_kudos(),
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlalchemy import JSON, func, or_
|
||||
from horde.logger import logger
|
||||
from horde.flask import db, SQLITE_MODE
|
||||
from horde import vars as hv
|
||||
from horde.utils import is_profane, get_db_uuid, get_expiry_date, get_db_uuid
|
||||
from horde.utils import is_profane, get_db_uuid, get_expiry_date
|
||||
|
||||
from horde.classes.base.processing_generation import ProcessingGeneration
|
||||
from horde.classes.stable.processing_generation import ImageProcessingGeneration
|
||||
@@ -82,6 +82,8 @@ class WaitingPrompt(db.Model):
|
||||
extra_priority = db.Column(db.Integer, default=0, nullable=False, index=True)
|
||||
job_ttl = db.Column(db.Integer, default=150, nullable=False)
|
||||
client_agent = db.Column(db.Text, default="unknown:0:unknown", nullable=False)
|
||||
sharedkey_id = db.Column(uuid_column_type(), db.ForeignKey("user_sharedkeys.id", ondelete="CASCADE"), nullable=True)
|
||||
sharedkey = db.relationship("UserSharedKey", back_populates="waiting_prompts")
|
||||
|
||||
tricked_workers = db.relationship("WPTrickedWorkers", back_populates="wp", passive_deletes=True, cascade="all, delete-orphan")
|
||||
workers = db.relationship("WPAllowedWorkers", back_populates="wp", passive_deletes=True, cascade="all, delete-orphan")
|
||||
@@ -296,6 +298,8 @@ class WaitingPrompt(db.Model):
|
||||
We use 'thing' here as we do not care what type of thing we're recording at this point
|
||||
This avoids me having to extend this just to change a var name
|
||||
'''
|
||||
if self.sharedkey_id is not None:
|
||||
self.sharedkey.consume_kudos(kudos)
|
||||
self.user.record_usage(raw_things, kudos, usage_type)
|
||||
self.consumed_kudos = round(self.consumed_kudos + kudos,2)
|
||||
self.refresh()
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
HORDE_VERSION = "4.11.2"
|
||||
HORDE_VERSION = "4.12.0"
|
||||
|
||||
WHITELISTED_SERVICE_IPS = {
|
||||
"212.227.227.178", # Turing Bot
|
||||
|
||||
@@ -16,7 +16,7 @@ from horde import vars as hv
|
||||
from horde.classes.base.worker import WorkerPerformance
|
||||
from horde.classes.stable.worker import ImageWorker
|
||||
from horde.classes.kobold.worker import TextWorker
|
||||
from horde.classes.base.user import User, UserRecords
|
||||
from horde.classes.base.user import User, UserRecords, UserSharedKey
|
||||
from horde.classes.stable.waiting_prompt import ImageWaitingPrompt
|
||||
from horde.classes.stable.processing_generation import ImageProcessingGeneration
|
||||
from horde.classes.kobold.waiting_prompt import TextWaitingPrompt
|
||||
@@ -175,6 +175,38 @@ def find_user_by_api_key(api_key):
|
||||
user = db.session.query(User).filter_by(api_key=hash_api_key(api_key)).first()
|
||||
return user
|
||||
|
||||
def find_user_by_sharedkey(shared_key):
|
||||
try:
|
||||
sharedkey_uuid = uuid.UUID(shared_key)
|
||||
except ValueError as e:
|
||||
logger.debug(f"Non-UUID sharedkey_id sent: '{shared_key}'.")
|
||||
return None
|
||||
if SQLITE_MODE:
|
||||
sharedkey_uuid = str(sharedkey_uuid)
|
||||
user = db.session.query(
|
||||
User
|
||||
).join(
|
||||
UserSharedKey
|
||||
).filter(
|
||||
UserSharedKey.id == shared_key
|
||||
).first()
|
||||
return user
|
||||
|
||||
def find_sharedkey(shared_key):
|
||||
try:
|
||||
sharedkey_uuid = uuid.UUID(shared_key)
|
||||
except ValueError as e:
|
||||
logger.debug(f"Non-UUID sharedkey_id sent: '{shared_key}'.")
|
||||
return None
|
||||
if SQLITE_MODE:
|
||||
sharedkey_uuid = str(sharedkey_uuid)
|
||||
sharedkey = db.session.query(
|
||||
UserSharedKey
|
||||
).filter(
|
||||
UserSharedKey.id == shared_key
|
||||
).first()
|
||||
return sharedkey
|
||||
|
||||
def find_worker_by_name(worker_name, worker_class=ImageWorker):
|
||||
worker = db.session.query(worker_class).filter_by(name=worker_name).first()
|
||||
return worker
|
||||
|
||||
+6
-3
@@ -103,9 +103,12 @@ class InvalidAestheticAttempt(wze.BadRequest):
|
||||
self.log = None
|
||||
|
||||
class InvalidAPIKey(wze.Unauthorized):
|
||||
def __init__(self, subject):
|
||||
self.specific = "No user matching sent API Key. Have you remembered to register at https://stablehorde.net/register ?"
|
||||
self.log = f"Invalid API Key sent for {subject}"
|
||||
def __init__(self, subject, keytype='API'):
|
||||
if keytype == "Shared":
|
||||
self.specific = "No user matching sent Shared Key."
|
||||
else:
|
||||
self.specific = "No user matching sent API Key. Have you remembered to register at https://stablehorde.net/register ?"
|
||||
self.log = f"Invalid {keytype} Key sent for {subject}"
|
||||
|
||||
class WrongCredentials(wze.Forbidden):
|
||||
def __init__(self, username, worker):
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE waiting_prompts ADD COLUMN sharedkey_id UUID;
|
||||
Reference in new issue
Block a user