diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py
index 6a8b1886ec640746637d4263c5fc297220a6052e..929a900a796da424c0ae47ca51a1c345a9afbc6f 100644
--- a/blenderkit/__init__.py
+++ b/blenderkit/__init__.py
@@ -83,7 +83,8 @@ def scene_load(context):
     ui_props = bpy.context.scene.blenderkitUI
     ui_props.assetbar_on = False
     ui_props.turn_off = False
-
+    preferences = bpy.context.preferences.addons['blenderkit'].preferences
+    preferences.login_attempt = False
 
 licenses = (
     ('royalty_free', 'Royalty Free', 'royalty free commercial license'),
@@ -205,14 +206,19 @@ def switch_search_results(self, context):
     props = s.blenderkitUI
     if props.asset_type == 'MODEL':
         s['search results'] = s.get('bkit model search')
+        s['search results orig'] = s.get('bkit model search orig')
     elif props.asset_type == 'SCENE':
         s['search results'] = s.get('bkit scene search')
+        s['search results orig'] = s.get('bkit scene search orig')
     elif props.asset_type == 'MATERIAL':
         s['search results'] = s.get('bkit material search')
+        s['search results orig'] = s.get('bkit material search orig')
     elif props.asset_type == 'TEXTURE':
         s['search results'] = s.get('bkit texture search')
+        s['search results orig'] = s.get('bkit texture search orig')
     elif props.asset_type == 'BRUSH':
         s['search results'] = s.get('bkit brush search')
+        s['search results orig'] = s.get('bkit brush search orig')
     search.load_previews()
 
 
@@ -1313,9 +1319,8 @@ class BlenderKitAddonPreferences(AddonPreferences):
         layout = self.layout
 
         if self.api_key.strip() == '':
-            op = layout.operator("wm.url_open", text="Register online and get your API Key",
-                                 icon='QUESTION')
-            op.url = paths.BLENDERKIT_SIGNUP_URL
+            layout.operator("wm.blenderkit_login", text="Login/ Sign up",
+                            icon='URL')
         layout.prop(self, "api_key", text='Your API Key')
         # layout.label(text='After you paste API Key, categories are downloaded, so blender will freeze for a few seconds.')
         layout.prop(self, "global_dir")
diff --git a/blenderkit/oauth.py b/blenderkit/oauth.py
index 78c8dfb37d0db48b29e9629a494fe752b22a287c..f2cdf3e84cfbb9afec392e4ee3b6bfd95124c353 100644
--- a/blenderkit/oauth.py
+++ b/blenderkit/oauth.py
@@ -25,6 +25,7 @@ from urllib.parse import parse_qs, urlparse
 
 import requests
 import threading
+import blenderkit
 from blenderkit import tasks_queue, utils, paths
 
 CLIENT_ID = "IdFRwa3SGA8eMpzhRVFMg5Ts8sPK93xBjif93x0F"
@@ -99,8 +100,8 @@ def login_thread():
 def login():
     authenticator = SimpleOAuthAuthenticator(server_url=paths.get_bkit_url(), client_id=CLIENT_ID, ports=PORTS)
     auth_token, refresh_token = authenticator.get_new_token()
-    print('tokens retrieved')
-    tasks_queue.tasks_queue.put('blenderkit.oauth.write_tokens("%s", "%s")' % (auth_token, refresh_token))
+    utils.p('tokens retrieved')
+    tasks_queue.add_task((write_tokens , (auth_token, refresh_token)))
 
 
 def refresh_token_thread():
@@ -113,11 +114,12 @@ def refresh_token_thread():
 def refresh_token(api_key_refresh):
     authenticator = SimpleOAuthAuthenticator(server_url=paths.get_bkit_url(), client_id=CLIENT_ID, ports=PORTS)
     auth_token, refresh_token = authenticator.get_refreshed_token(api_key_refresh)
-    tasks_queue.tasks_queue.put('blenderkit.oauth.write_tokens("%s", "%s")' % (auth_token, refresh_token))
+    if auth_token is not None and refresh_token is not None:
+        tasks_queue.add_task((blenderkit.oauth.write_tokens , (auth_token, refresh_token)))
 
 
 def write_tokens(auth_token, refresh_token):
-    print('writing tokens?')
+    utils.p('writing tokens?')
     preferences = bpy.context.preferences.addons['blenderkit'].preferences
     preferences.api_key = auth_token
     preferences.api_key_refresh = refresh_token
@@ -144,6 +146,25 @@ class RegisterLoginOnline(bpy.types.Operator):
         return {'FINISHED'}
 
 
+class Logout(bpy.types.Operator):
+    """Bring linked object hierarchy to scene and make it editable."""
+
+    bl_idname = "wm.blenderkit_logout"
+    bl_label = "BlenderKit logout"
+    bl_options = {'REGISTER', 'UNDO'}
+
+    @classmethod
+    def poll(cls, context):
+        return True
+
+    def execute(self, context):
+        preferences = bpy.context.preferences.addons['blenderkit'].preferences
+        preferences.login_attempt = False
+        preferences.api_key_refresh = ''
+        preferences.api_key = ''
+        return {'FINISHED'}
+
+
 class CancelLoginOnline(bpy.types.Operator):
     """Cancel login attempt."""
 
@@ -163,6 +184,7 @@ class CancelLoginOnline(bpy.types.Operator):
 classess = (
     RegisterLoginOnline,
     CancelLoginOnline,
+    Logout,
 )
 
 
diff --git a/blenderkit/search.py b/blenderkit/search.py
index 39a770eeebd0fcfad647f88bc9a97560ceeb9189..458653915867fddc265b058659643c0c54f7d88f 100644
--- a/blenderkit/search.py
+++ b/blenderkit/search.py
@@ -25,8 +25,9 @@ if "bpy" in locals():
     imp.reload(ui)
     imp.reload(version_checker)
     imp.reload(oauth)
+    imp.reload(tasks_queue)
 else:
-    from blenderkit import paths, utils, categories, ui, oauth, version_checker
+    from blenderkit import paths, utils, categories, ui, oauth, version_checker, tasks_queue
 
 import blenderkit
 from bpy.app.handlers import persistent
@@ -91,6 +92,7 @@ def fetch_server_data():
     api_key = user_preferences.api_key
     # version_checker.check_version_thread(url, api_key, blenderkit)
     oauth.refresh_token_thread()
+    get_profile()
     categories.fetch_categories_thread(api_key)
 
 
@@ -177,7 +179,9 @@ def timer_update():  # TODO might get moved to handle all blenderkit stuff.
                                               'tooltip': tooltip,
                                               'tags': r['tags'],
                                               'can_download': r.get('canDownload', True),
-                                              'verification_status': r['verificationStatus']
+                                              'verification_status': r['verificationStatus'],
+                                              'author_id': str(r['author']['id'])
+                                              # 'author': r['author']['firstName'] + ' ' + r['author']['lastName']
                                               # 'description': r['description'],
                                               # 'author': r['description'],
                                               }
@@ -217,6 +221,7 @@ def timer_update():  # TODO might get moved to handle all blenderkit stuff.
                                 # results = rdata['results']
                 s[search_name] = result_field
                 s['search results'] = result_field
+                s[search_name + ' orig'] = rdata
                 s['search results orig'] = rdata
                 load_previews()
                 ui_props = bpy.context.scene.blenderkitUI
@@ -432,17 +437,35 @@ def generate_tooltip(mdata):
     # generator is for both upload preview and search, this is only after search
     if mdata.get('versionNumber'):
         # t = writeblockm(t, mdata, key='versionNumber', pretext='version')
-
-        t += 'author: %s %s\n' % (mdata['author']['firstName'], mdata['author']['lastName'])
-        # t += '\n'
+        a_id = mdata['author'].get('id')
+        if a_id != None:
+            adata = bpy.context.window_manager['bkit authors'].get(str(a_id))
+            if adata != None:
+                t += generate_author_textblock(adata)
 
     at = mdata['assetType']
     t += '\n'
+
+    return t
+
+def get_random_tip():
     if at == 'brush' or at == 'texture':
         t += 'click to link %s' % mdata['assetType']
     if at == 'model' or at == 'material':
-        t += 'click or drag in scene to link/append %s' % mdata['assetType']
-
+        tips = ['Click or drag in scene to link/append %s' % mdata['assetType'],
+                # "'A' key to search assets by same author",
+                "'W' key to open Authors webpage",
+                ]
+        tip = 'Tip: ' + random.choice(tips)
+        t = writeblock(t, tip)
+
+def generate_author_textblock(adata):
+    t = ''
+    if adata not in (None, ''):
+        t += 'author: %s %s\n' % (adata['firstName'], adata['lastName'])
+        t = writeblockm(t, adata, key='aboutMe', pretext='')
+        t += '\n'
+        t = writeblockm(t, adata, key='aboutMeUrl', pretext='')
     return t
 
 
@@ -492,6 +515,80 @@ class ThumbDownloader(threading.Thread):
             #         f.write(chunk)
 
 
+def write_author(a_id, adata):
+    utils.p('writing author back')
+    authors = bpy.context.window_manager['bkit authors']
+    if authors.get(a_id) in (None, ''):
+        adata['tooltip'] = generate_author_textblock
+        authors[a_id] = adata
+
+
+def fetch_author(a_id, api_key):
+    utils.p('fetch author')
+    try:
+        a_url = paths.get_api_url() + 'accounts/' + a_id + '/'
+        headers = utils.get_headers(api_key)
+        r = requests.get(a_url, headers=headers)
+        adata = r.json()
+        if not hasattr(adata, 'id'):
+            utils.p(adata)
+        # utils.p(adata)
+        tasks_queue.add_task((write_author, (a_id, adata)))
+    except Exception as e:
+        utils.p(e)
+    utils.p('finish fetch')
+
+
+def get_author(r):
+    a_id = str(r['author']['id'])
+    preferences = bpy.context.preferences.addons['blenderkit'].preferences
+
+    authors = bpy.context.window_manager.get('bkit authors', {})
+    if authors == {}:
+        bpy.context.window_manager['bkit authors'] = authors
+    a = authors.get(a_id)
+    if a is None or a is '':
+        authors[a_id] = ''
+        thread = threading.Thread(target=fetch_author, args=(a_id, preferences.api_key), daemon=True)
+        thread.start()
+    return a
+
+
+def write_profile(adata):
+    utils.p('writing profile')
+    utils.p(adata.keys())
+    adata['user']['sumAssetFilesSize'] = str(round(adata['user']['sumAssetFilesSize'] / 1024 / 1024)) + ' Mb'
+    adata['user']['sumPrivateAssetFilesSize'] = str(
+        round(adata['user']['sumPrivateAssetFilesSize'] / 1024 / 1024)) + ' Mb'
+    adata['user']['remainingPrivateQuota'] = str(round(adata['user']['remainingPrivateQuota'] / 1024 / 1024)) + ' Mb'
+    bpy.context.window_manager['bkit profile'] = adata
+
+
+def fetch_profile(api_key):
+    utils.p('fetch profile')
+    try:
+        a_url = paths.get_api_url() + 'me/'
+        headers = utils.get_headers(api_key)
+        r = requests.get(a_url, headers=headers)
+        adata = r.json()
+        if not hasattr(adata, 'user'):
+            utils.p(adata)
+            utils.p('getting profile failed')
+            return
+        tasks_queue.add_task((write_profile, (adata,)))
+    except Exception as e:
+        utils.p(e)
+
+
+def get_profile():
+    preferences = bpy.context.preferences.addons['blenderkit'].preferences
+    a = bpy.context.window_manager.get('bkit profile')
+    if a is None:
+        thread = threading.Thread(target=fetch_profile, args=(preferences.api_key,), daemon=True)
+        thread.start()
+    return a
+
+
 class Searcher(threading.Thread):
     query = None
 
@@ -561,10 +658,10 @@ class Searcher(threading.Thread):
             urlquery = url + requeststring
 
         try:
-            # print(urlquery)
+            # utils.p(urlquery)
             r = requests.get(urlquery, headers=headers)
             reports = ''
-            # print(r.text)
+            # utils.p(r.text)
         except requests.exceptions.RequestException as e:
             print(e)
             reports = e
@@ -596,7 +693,7 @@ class Searcher(threading.Thread):
 
         # print('number of results: ', len(rdata.get('results', [])))
         if self.stopped():
-            print('stopping search : ' + query['keywords'])
+            utils.p('stopping search : ' + query['keywords'])
             return
 
         mt('search finished')
@@ -607,7 +704,12 @@ class Searcher(threading.Thread):
         thumb_full_urls = []
         thumb_full_filepaths = []
         # END OF PARSING
+        getting_authors = {}
         for d in rdata.get('results', []):
+            if getting_authors.get(d['author']['id']) is None:
+                get_author(d)
+                getting_authors[d['author']['id']] = True
+
             for f in d['files']:
                 # TODO move validation of published assets to server, too manmy checks here.
                 if f['fileType'] == 'thumbnail' and f['fileThumbnail'] != None and f['fileThumbnailLarge'] != None:
@@ -650,7 +752,7 @@ class Searcher(threading.Thread):
         # TODO do the killing/ stopping here! remember threads might have finished inbetween!
 
         if self.stopped():
-            print('stopping search : ' + query['keywords'])
+            utils.p('stopping search : ' + query['keywords'])
             return
 
         # this loop handles downloading of small thumbnails
@@ -669,12 +771,12 @@ class Searcher(threading.Thread):
                         for tk, thread in threads_copy.items():
                             if not thread.is_alive():
                                 thread.join()
-                                # print(x)
+                                # utils.p(x)
                                 del (thumb_sml_download_threads[tk])
-                                # print('fetched thumbnail ', i)
+                                # utils.p('fetched thumbnail ', i)
                                 i += 1
         if self.stopped():
-            print('stopping search : ' + query['keywords'])
+            utils.p('stopping search : ' + query['keywords'])
             return
         idx = 0
         while len(thumb_sml_download_threads) > 0:
@@ -686,7 +788,7 @@ class Searcher(threading.Thread):
                     i += 1
 
         if self.stopped():
-            print('stopping search : ' + query['keywords'])
+            utils.p('stopping search : ' + query['keywords'])
             return
 
         # start downloading full thumbs in the end
@@ -842,7 +944,7 @@ def mt(text):
     alltime = time.time() - search_start_time
     since_last = time.time() - prev_time
     prev_time = time.time()
-    print(text, alltime, since_last)
+    utils.p(text, alltime, since_last)
 
 
 def add_search_process(query, params):
@@ -908,7 +1010,7 @@ def search(own=False, category='', get_next=False, free_only=False):
     if category != '':
         query['category'] = category
 
-    # print('searching')
+    # utils.p('searching')
     props.is_searching = True
 
     params = {
@@ -925,6 +1027,7 @@ def search(own=False, category='', get_next=False, free_only=False):
 
 
 def search_update(self, context):
+    utils.p('search updater')
     if self.search_keywords != '':
         search()
 
diff --git a/blenderkit/tasks_queue.py b/blenderkit/tasks_queue.py
index 08ac90bf7c7fc420012f842b6505847c853cdc12..55029753c433239ee218ec7c2d68988a84709c87 100644
--- a/blenderkit/tasks_queue.py
+++ b/blenderkit/tasks_queue.py
@@ -2,19 +2,37 @@ import bpy
 
 import queue
 
-import blenderkit
+from blenderkit import utils
+
+def get_queue():
+    if not hasattr(bpy.types.VIEW3D_PT_blenderkit_unified, 'task_queue'):
+        bpy.types.VIEW3D_PT_blenderkit_unified.task_queue = queue.Queue()
+    return bpy.types.VIEW3D_PT_blenderkit_unified.task_queue
+
+
+def add_task(task):
+    q = get_queue()
+    q.put(task)
 
-tasks_queue = queue.Queue()
 
 def every_2_seconds():
-    while not tasks_queue.empty():
-        print('as a task:   ')
-        fstring = tasks_queue.get()
-        eval(fstring)
+    q = get_queue()
+
+    while not q.empty():
+        utils.p('as a task:   ')
+        q = bpy.types.VIEW3D_PT_blenderkit_unified.task_queue
+        task = q.get()
+        try:
+            task[0](*task[1])
+        except Exception as e:
+            utils.p('task failed:')
+            print(e)
     return 2.0
 
+
 def register():
     bpy.app.timers.register(every_2_seconds)
 
+
 def unregister():
-    bpy.app.timers.unregister(every_2_seconds)
\ No newline at end of file
+    bpy.app.timers.unregister(every_2_seconds)
diff --git a/blenderkit/ui.py b/blenderkit/ui.py
index b0ac920939a16d556981ff908fd5b33010022db0..e26b24abdace8121f952a717cd453d9abb0b947e 100644
--- a/blenderkit/ui.py
+++ b/blenderkit/ui.py
@@ -493,6 +493,7 @@ def draw_callback_2d_search(self, context):
     # background of asset bar
     if not ui_props.dragging:
         search_results = s.get('search results')
+        search_results_orig = s.get('search results orig')
         if search_results == None:
             return
         h_draw = min(ui_props.hcount, math.ceil(len(search_results) / ui_props.wcount))
@@ -524,7 +525,7 @@ def draw_callback_2d_search(self, context):
                                       ui_props.thumb_size,
                                       img,
                                       1)
-                if len(search_results) - ui_props.scrolloffset > (ui_props.wcount * ui_props.hcount):
+                if search_results_orig['count'] - ui_props.scrolloffset > (ui_props.wcount * ui_props.hcount):
                     if ui_props.active_index == -1:
                         ui_bgl.draw_rect(ui_props.bar_x + ui_props.bar_width - 25,
                                          ui_props.bar_y - ui_props.bar_height, 25,
@@ -913,8 +914,8 @@ class AssetBarOperator(bpy.types.Operator):
         default="", options={'SKIP_SAVE'})
 
     def search_more(self):
-        sro = bpy.context.scene.get('search results orig', {})
-        if sro.get('next') != None:
+        sro = bpy.context.scene.get('search results orig')
+        if sro is not None and sro.get('next') is not None:
             search.search(get_next=True)
 
     def exit_modal(self):
@@ -1311,6 +1312,13 @@ class AssetBarOperator(bpy.types.Operator):
             else:
                 return {'RUNNING_MODAL'}
 
+        if event.type == 'A' and ui_props.active_index != -3:
+            sr = bpy.context.scene['search results']
+            asset_data = sr[ui_props.active_index]
+            a = bpy.context.window_manager['bkit authors'].get(asset_data['author_id'])
+            if a is not None:
+                if a.get('aboutMeUrl') is not None:
+                    bpy.ops.wm.url_open(url=a['aboutMeUrl'])
         if event.type == 'X' and ui_props.active_index != -3:
             sr = bpy.context.scene['search results']
             asset_data = sr[ui_props.active_index]
diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py
index 1047eb98f2c52340306314885e28d9c570d6f1dd..f97a9088ff8828904249e24e0430f434e2aa4ffd 100644
--- a/blenderkit/ui_panels.py
+++ b/blenderkit/ui_panels.py
@@ -44,7 +44,7 @@ def label_multiline(layout, text='', icon='NONE', width=-1):
     else:
         threshold = 35
     maxlines = 3
-    li =0
+    li = 0
     for l in lines:
         while len(l) > threshold:
             i = l.rfind(' ', 0, threshold)
@@ -54,10 +54,10 @@ def label_multiline(layout, text='', icon='NONE', width=-1):
             layout.label(text=l1, icon=icon)
             icon = 'NONE'
             l = l[i:]
-            li+=1
+            li += 1
             if li > maxlines:
                 break;
-        if li>maxlines:
+        if li > maxlines:
             break;
         layout.label(text=l, icon=icon)
         icon = 'NONE'
@@ -348,7 +348,7 @@ def draw_panel_scene_search(self, context):
     # layout.prop(props, "search_style")
     # if props.search_style == 'OTHER':
     #     layout.prop(props, "search_style_other")
-    #layout.prop(props, "search_engine")
+    # layout.prop(props, "search_engine")
     layout.separator()
     draw_panel_categories(self, context)
 
@@ -382,6 +382,45 @@ class VIEW3D_PT_blenderkit_model_properties(Panel):
         # layout.operator('object.blenderkit_color_corrector')
 
 
+class VIEW3D_PT_blenderkit_profile(Panel):
+    bl_category = "BlenderKit"
+    bl_idname = "VIEW3D_PT_blenderkit_profile"
+    bl_space_type = 'VIEW_3D'
+    bl_region_type = 'UI'
+    bl_label = "Profile"
+
+    @classmethod
+    def poll(cls, context):
+        return True
+
+    def draw(self, context):
+        # draw asset properties here
+        layout = self.layout
+        user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+
+        if user_preferences.login_attempt:
+            layout.label(text='Login through browser')
+            layout.label(text='in progress.')
+            layout.operator("wm.blenderkit_login_cancel", text="Cancel", icon='CANCEL')
+            return
+
+        if len(user_preferences.api_key) < 20:
+            layout.operator("wm.blenderkit_login", text="Login/ Sign up",
+                            icon='URL')
+
+        else:
+            me = bpy.context.window_manager.get('bkit profile')
+            if me is not None:
+                me = me['user']
+                layout.label(text='User: %s %s' % (me['firstName'], me['lastName']))
+                layout.label(text='Email: %s' % (me['email']))
+                layout.label(text='Public assets sum: %s ' % (me['sumAssetFilesSize']))
+                layout.label(text='Private assets sum: %s ' % (me['sumPrivateAssetFilesSize']))
+                layout.label(text='Remaining private storage: %s' % (me['remainingPrivateQuota']))
+            layout.operator("wm.blenderkit_logout", text="Logout",
+                            icon='URL')
+
+
 def draw_panel_model_rating(self, context):
     o = bpy.context.active_object
     op = draw_ratings(self.layout, context)  # , props)
@@ -524,14 +563,14 @@ class VIEW3D_PT_blenderkit_unified(Panel):
 
         w = context.region.width
         if user_preferences.login_attempt:
-            layout.label(text = 'Login through browser')
-            layout.label(text = 'in progress.')
-            layout.operator("wm.blenderkit_login_cancel", text = "Cancel", icon = 'CANCEL')
+            layout.label(text='Login through browser')
+            layout.label(text='in progress.')
+            layout.operator("wm.blenderkit_login_cancel", text="Cancel", icon='CANCEL')
             return
 
-        if len(user_preferences.api_key) < 20 and user_preferences.asset_counter >-10:
+        if len(user_preferences.api_key) < 20 and user_preferences.asset_counter > 5:
             layout.operator("wm.blenderkit_login", text="Login/ Sign up",
-                                 icon='URL')
+                            icon='URL')
             # layout.label(text='Paste your API Key:')
             # layout.prop(user_preferences, 'api_key', text='')
             layout.separator()
@@ -759,7 +798,7 @@ classess = (
     VIEW3D_PT_blenderkit_unified,
     VIEW3D_PT_blenderkit_model_properties,
     VIEW3D_PT_blenderkit_downloads,
-
+    VIEW3D_PT_blenderkit_profile
 )
 
 
diff --git a/blenderkit/utils.py b/blenderkit/utils.py
index cebc2eaf0af4eadea5bfcf5e6773bf9d1f8e7a2a..1a1d4ed79ac3610cfa668dd34c6fc4642073426d 100644
--- a/blenderkit/utils.py
+++ b/blenderkit/utils.py
@@ -173,7 +173,6 @@ def load_prefs():
             prefs = json.load(s)
             user_preferences.api_key = prefs.get('API_key','')
             user_preferences.global_dir = prefs.get('global_dir', paths.default_global_dict())
-
             user_preferences.api_key_refresh = prefs.get('API_key_refresh','')
 
 def save_prefs(self, context):
@@ -268,6 +267,9 @@ def get_brush_props(context):
         return brush.blenderkit
     return None
 
+def p(text):
+    if bpy.app.debug != 0:
+        print(p)
 
 def pprint(data):
     print(json.dumps(data, indent=4, sort_keys=True))