search endpoint (user part)

This commit is contained in:
Mouse Reeve 2020-01-27 19:57:17 -08:00
parent d2d278b475
commit f7242452fa
6 changed files with 44 additions and 17 deletions

View file

@ -19,11 +19,11 @@ def webfinger(request):
if not resource and not resource.startswith('acct:'): if not resource and not resource.startswith('acct:'):
return HttpResponseBadRequest() return HttpResponseBadRequest()
ap_id = resource.replace('acct:', '') ap_id = resource.replace('acct:', '')
user = models.User.objects.filter(full_username=ap_id).first() user = models.User.objects.filter(username=ap_id).first()
if not user: if not user:
return HttpResponseNotFound('No account found') return HttpResponseNotFound('No account found')
return JsonResponse({ return JsonResponse({
'subject': 'acct:%s' % (user.full_username), 'subject': 'acct:%s' % (user.username),
'links': [ 'links': [
{ {
'rel': 'self', 'rel': 'self',
@ -88,6 +88,22 @@ def inbox(request, username):
return HttpResponse() return HttpResponse()
def handle_account_search(query):
''' webfingerin' other servers '''
domain = query.split('@')[1]
try:
user = models.User.objects.get(username=query)
except models.User.DoesNotExist:
url = 'https://%s/.well-known/webfinger' % domain
params = {'resource': 'acct:%s' % query}
response = requests.get(url, params=params)
data = response.json()
for link in data['links']:
if link['rel'] == 'self':
user = get_or_create_remote_user(link['href'])
return user
def handle_add(activity): def handle_add(activity):
''' receiving an Add activity (to shelve a book) ''' ''' receiving an Add activity (to shelve a book) '''
# TODO what happens here? If it's a remote over, then I think # TODO what happens here? If it's a remote over, then I think
@ -156,7 +172,7 @@ def handle_outgoing_follow(user, to_follow):
'summary': '', 'summary': '',
'type': 'Follow', 'type': 'Follow',
'actor': user.actor, 'actor': user.actor,
'object': to_follow, 'object': to_follow.actor,
} }
broadcast(user, activity, [format_inbox(to_follow)]) broadcast(user, activity, [format_inbox(to_follow)])
@ -281,6 +297,7 @@ def broadcast(sender, action, recipients):
for recipient in recipients: for recipient in recipients:
sign_and_send(sender, action, recipient) sign_and_send(sender, action, recipient)
def sign_and_send(sender, action, destination): def sign_and_send(sender, action, destination):
''' crpyto whatever and http junk ''' ''' crpyto whatever and http junk '''
inbox_fragment = '/api/u/%s/inbox' % (sender.username) inbox_fragment = '/api/u/%s/inbox' % (sender.username)
@ -291,7 +308,7 @@ date: %s''' % (inbox_fragment, DOMAIN, now)
signer = pkcs1_15.new(RSA.import_key(sender.private_key)) signer = pkcs1_15.new(RSA.import_key(sender.private_key))
signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8'))) signed_message = signer.sign(SHA256.new(message_to_sign.encode('utf8')))
signature = 'keyId="%s",' % sender.full_username signature = 'keyId="%s",' % sender.username
signature += 'headers="(request-target) host date",' signature += 'headers="(request-target) host date",'
signature += 'signature="%s"' % b64encode(signed_message) signature += 'signature="%s"' % b64encode(signed_message)
response = requests.post( response = requests.post(
@ -306,9 +323,9 @@ date: %s''' % (inbox_fragment, DOMAIN, now)
if not response.ok: if not response.ok:
response.raise_for_status() response.raise_for_status()
def get_or_create_remote_user(activity):
def get_or_create_remote_user(actor):
''' wow, a foreigner ''' ''' wow, a foreigner '''
actor = activity['actor']
try: try:
user = models.User.objects.get(actor=actor) user = models.User.objects.get(actor=actor)
except models.User.DoesNotExist: except models.User.DoesNotExist:

View file

@ -1,4 +1,4 @@
# Generated by Django 3.0.2 on 2020-01-28 02:46 # Generated by Django 3.0.2 on 2020-01-28 03:40
from django.conf import settings from django.conf import settings
import django.contrib.auth.models import django.contrib.auth.models
@ -32,7 +32,6 @@ class Migration(migrations.Migration):
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('full_username', models.CharField(blank=True, max_length=255, null=True, unique=True)),
('private_key', models.TextField(blank=True, null=True)), ('private_key', models.TextField(blank=True, null=True)),
('public_key', models.TextField(blank=True, null=True)), ('public_key', models.TextField(blank=True, null=True)),
('api_key', models.CharField(blank=True, max_length=255, null=True)), ('api_key', models.CharField(blank=True, max_length=255, null=True)),

View file

@ -10,12 +10,6 @@ import re
class User(AbstractUser): class User(AbstractUser):
''' a user who wants to read books ''' ''' a user who wants to read books '''
full_username = models.CharField(
max_length=255,
blank=True,
null=True,
unique=True
)
private_key = models.TextField(blank=True, null=True) private_key = models.TextField(blank=True, null=True)
public_key = models.TextField(blank=True, null=True) public_key = models.TextField(blank=True, null=True)
api_key = models.CharField(max_length=255, blank=True, null=True) api_key = models.CharField(max_length=255, blank=True, null=True)
@ -36,8 +30,8 @@ class User(AbstractUser):
if self.local and not self.actor: if self.local and not self.actor:
self.actor = 'https://%s/api/u/%s' % (DOMAIN, self.username) self.actor = 'https://%s/api/u/%s' % (DOMAIN, self.username)
if self.local and not self.full_username: if self.local and not re.match(r'\w+@\w+.\w+', self.username):
self.full_username = '%s@%s' % (self.username, DOMAIN) self.username = '%s@%s' % (self.username, DOMAIN)
super().save(*args, **kwargs) super().save(*args, **kwargs)

View file

@ -38,7 +38,7 @@
{% endif %} {% endif %}
</div> </div>
<div id="search"> <div id="search">
<form action="search"> <form action="/search/">
<input type="text" name="q"></input> <input type="text" name="q"></input>
<input type="submit" value="🔍"></input> <input type="submit" value="🔍"></input>
</form> </form>

View file

@ -24,10 +24,13 @@ urlpatterns = [
path('logout/', views.user_logout), path('logout/', views.user_logout),
path('user/<str:username>', views.user_profile), path('user/<str:username>', views.user_profile),
path('book/<str:book_identifier>', views.book_page), path('book/<str:book_identifier>', views.book_page),
path('review/', views.review), path('review/', views.review),
path('shelve/<str:shelf_id>/<int:book_id>', views.shelve), path('shelve/<str:shelf_id>/<int:book_id>', views.shelve),
path('follow/', views.follow), path('follow/', views.follow),
path('unfollow/', views.unfollow), path('unfollow/', views.unfollow),
path('search/', views.search),
path('api/u/<str:username>', federation.get_actor), path('api/u/<str:username>', federation.get_actor),
path('api/u/<str:username>/inbox', federation.inbox), path('api/u/<str:username>/inbox', federation.inbox),
path('api/u/<str:username>/outbox', federation.outbox), path('api/u/<str:username>/outbox', federation.outbox),

View file

@ -7,6 +7,7 @@ from django.template.response import TemplateResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from fedireads import models, openlibrary from fedireads import models, openlibrary
from fedireads import federation as api from fedireads import federation as api
import re
@login_required @login_required
def home(request): def home(request):
@ -133,3 +134,16 @@ def unfollow(request):
followed.followers.remove(request.user) followed.followers.remove(request.user)
return redirect('/user/%s' % followed.username) return redirect('/user/%s' % followed.username)
@csrf_exempt
@login_required
def search(request):
''' that search bar up top '''
query = request.GET.get('q')
if re.match(r'\w+@\w+.\w+', query):
results = [api.handle_account_search(query)]
else:
results = []
return TemplateResponse(request, 'results.html', {'results': results})